{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Справочник"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Токенизатор"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Токенизатор в Yargy реализован на регулярных выражениях. Для каждого типа токена есть правило с регуляркой:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[TokenRule(\n",
       "     type='RU',\n",
       "     pattern='[а-яё]+'\n",
       " ),\n",
       " TokenRule(\n",
       "     type='LATIN',\n",
       "     pattern='[a-z]+'\n",
       " ),\n",
       " TokenRule(\n",
       "     type='INT',\n",
       "     pattern='\\\\d+'\n",
       " ),\n",
       " TokenRule(\n",
       "     type='PUNCT',\n",
       "     pattern='[-\\\\\\\\/!#$%&()\\\\[\\\\]\\\\*\\\\+,\\\\.:;<=>?@^_`{|}~№…\"\\\\\\'«»„“ʼʻ”]'\n",
       " ),\n",
       " TokenRule(\n",
       "     type='EOL',\n",
       "     pattern='[\\\\n\\\\r]+'\n",
       " ),\n",
       " TokenRule(\n",
       "     type='OTHER',\n",
       "     pattern='\\\\S'\n",
       " )]"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from yargy.tokenizer import RULES\n",
    "\n",
    "\n",
    "RULES"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Токенизатор инициализируется списком правил. По-умолчанию — это `RULES`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[Token(\n",
       "     value='a',\n",
       "     span=[0, 1),\n",
       "     type='LATIN'\n",
       " ),\n",
       " Token(\n",
       "     value='@',\n",
       "     span=[1, 2),\n",
       "     type='PUNCT'\n",
       " ),\n",
       " Token(\n",
       "     value='mail',\n",
       "     span=[2, 6),\n",
       "     type='LATIN'\n",
       " ),\n",
       " Token(\n",
       "     value='.',\n",
       "     span=[6, 7),\n",
       "     type='PUNCT'\n",
       " ),\n",
       " Token(\n",
       "     value='ru',\n",
       "     span=[7, 9),\n",
       "     type='LATIN'\n",
       " )]"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from yargy.tokenizer import Tokenizer\n",
    "\n",
    "\n",
    "text = 'a@mail.ru'\n",
    "tokenizer = Tokenizer()\n",
    "list(tokenizer(text))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Пользователь может убрать часть правил из списка или добавить новые. Уберём токены с переводами строк:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[Token(\n",
       "     value='\\n',\n",
       "     span=[0, 1),\n",
       "     type='EOL'\n",
       " ),\n",
       " Token(\n",
       "     value='abc',\n",
       "     span=[1, 4),\n",
       "     type='LATIN'\n",
       " ),\n",
       " Token(\n",
       "     value='\\n',\n",
       "     span=[4, 5),\n",
       "     type='EOL'\n",
       " ),\n",
       " Token(\n",
       "     value='123',\n",
       "     span=[5, 8),\n",
       "     type='INT'\n",
       " ),\n",
       " Token(\n",
       "     value='\\n',\n",
       "     span=[8, 9),\n",
       "     type='EOL'\n",
       " )]"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "tokenizer = Tokenizer()\n",
    "\n",
    "text = '''\n",
    "abc\n",
    "123\n",
    "'''\n",
    "list(tokenizer(text))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Для этого удалим правило `EOL`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[Token(\n",
       "     value='abc',\n",
       "     span=[1, 4),\n",
       "     type='LATIN'\n",
       " ),\n",
       " Token(\n",
       "     value='123',\n",
       "     span=[5, 8),\n",
       "     type='INT'\n",
       " )]"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "tokenizer = Tokenizer().remove_types('EOL')\n",
    "\n",
    "list(tokenizer(text))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В Yargy есть примитивные правила для токенизации емейлов и телефонов. По-умолчанию они отключены:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[Token(\n",
       "     value='email',\n",
       "     span=[0, 5),\n",
       "     type='LATIN'\n",
       " ),\n",
       " Token(\n",
       "     value=':',\n",
       "     span=[5, 6),\n",
       "     type='PUNCT'\n",
       " ),\n",
       " Token(\n",
       "     value='ab@mail.ru',\n",
       "     span=[7, 17),\n",
       "     type='EMAIL'\n",
       " ),\n",
       " Token(\n",
       "     value='call',\n",
       "     span=[18, 22),\n",
       "     type='LATIN'\n",
       " ),\n",
       " Token(\n",
       "     value=':',\n",
       "     span=[22, 23),\n",
       "     type='PUNCT'\n",
       " ),\n",
       " Token(\n",
       "     value=' 8 915 132 54 76',\n",
       "     span=[23, 39),\n",
       "     type='PHONE'\n",
       " )]"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from yargy.tokenizer import EMAIL_RULE, PHONE_RULE\n",
    "\n",
    "\n",
    "text = 'email: ab@mail.ru call: 8 915 132 54 76'\n",
    "tokenizer = Tokenizer().add_rules(EMAIL_RULE, PHONE_RULE)\n",
    "list(tokenizer(text))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Добавим собственное для извлечения доменов:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[Token(\n",
       "     value='на',\n",
       "     span=[0, 2),\n",
       "     type='RU'\n",
       " ),\n",
       " Token(\n",
       "     value='сайте',\n",
       "     span=[3, 8),\n",
       "     type='RU'\n",
       " ),\n",
       " Token(\n",
       "     value='www.VKontakte.ru',\n",
       "     span=[9, 25),\n",
       "     type='DOMAIN'\n",
       " )]"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from yargy.tokenizer import TokenRule\n",
    "\n",
    "\n",
    "DOMAIN_RULE = TokenRule('DOMAIN', '[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+')\n",
    "\n",
    "    \n",
    "text = 'на сайте www.VKontakte.ru'\n",
    "tokenizer = Tokenizer().add_rules(DOMAIN_RULE)\n",
    "list(tokenizer(text))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "По умолчанию, Yargy использует не `Tokenizer`, а `MorphTokenizer`. Для каждого токена с типом `'RU'` он запускает Pymorphy2, добавляет поле `forms` с морфологией:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[Token(\n",
       "     value='X',\n",
       "     span=[0, 1),\n",
       "     type='LATIN'\n",
       " ),\n",
       " MorphToken(\n",
       "     value='век',\n",
       "     span=[2, 5),\n",
       "     type='RU',\n",
       "     forms=[Form('век', Grams(NOUN,inan,masc,nomn,sing)),\n",
       "      Form('век', Grams(NOUN,accs,inan,masc,sing)),\n",
       "      Form('век', Grams(ADVB)),\n",
       "      Form('веко', Grams(NOUN,gent,inan,neut,plur))]\n",
       " ),\n",
       " MorphToken(\n",
       "     value='стал',\n",
       "     span=[6, 10),\n",
       "     type='RU',\n",
       "     forms=[Form('стать', Grams(VERB,indc,intr,masc,past,perf,sing))]\n",
       " )]"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from yargy.tokenizer import MorphTokenizer\n",
    "\n",
    "tokenizer = MorphTokenizer()\n",
    "list(tokenizer('X век стал'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Газеттир"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Словарь профессий, географических объектов можно записывать стандартные средствами через `rule`, `or_`, `normalized`, `caseless`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "from yargy import rule, or_\n",
    "from yargy.predicates import normalized, caseless\n",
    "\n",
    "\n",
    "POSITION = or_(\n",
    "    rule(normalized('генеральный'), normalized('директор')),\n",
    "    rule(normalized('бухгалтер'))\n",
    ")\n",
    "\n",
    "GEO = or_(\n",
    "    rule(normalized('Ростов'), '-', caseless('на'), '-', caseless('Дону')),\n",
    "    rule(normalized('Москва'))\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Это неудобно, легко ошибиться. Для составления словарей в Yargy используется `pipeline`. Реализовано два типа газеттиров: `morph_pipeline` и `caseless_pipeline`. `morph_pipeline` перед работой приводит слова к нормальной форме:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['электронным', 'дневником']\n",
      "['электронные', 'дневники']\n",
      "['электронное', 'дневнику']\n"
     ]
    }
   ],
   "source": [
    "from yargy import Parser\n",
    "from yargy.pipelines import morph_pipeline\n",
    "\n",
    "\n",
    "TYPE = morph_pipeline(['электронный дневник'])\n",
    "\n",
    "parser = Parser(TYPE)\n",
    "text = 'электронным дневником, электронные дневники, электронное дневнику'\n",
    "for match in parser.findall(text):\n",
    "    print([_.value for _ in match.tokens])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`caseless_pipeline` ищет слова без нормализации. Например, найдём в тексте арабские имена: \"Абд Аль-Азиз Бин Мухаммад\", \"Абд ар-Рахман Наср ас-Са ди\": "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['Абд', 'Аль', '-', 'Азиз', 'Бин', 'Мухаммад']\n",
      "['АБД', 'АР', '-', 'РАХМАН', 'НАСР', 'АС', '-', 'СА', 'ДИ']\n"
     ]
    }
   ],
   "source": [
    "from yargy.pipelines import caseless_pipeline\n",
    "\n",
    "\n",
    "NAME = caseless_pipeline([\n",
    "    'Абд Аль-Азиз Бин Мухаммад',\n",
    "    'Абд ар-Рахман Наср ас-Са ди'\n",
    "])\n",
    "    \n",
    "parser = Parser(NAME)\n",
    "text = 'Абд Аль-Азиз Бин Мухаммад, АБД АР-РАХМАН НАСР АС-СА ДИ'\n",
    "for match in parser.findall(text):\n",
    "    print([_.value for _ in match.tokens])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Предикаты"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<h3>eq <a name=\"predicates.eq\"><a href=\"#predicates.eq\">#</a></h3>\n",
       "<br/><pre>    a == b\n",
       "\n",
       "    >>> predicate = eq('1')\n",
       "    >>> token, = tokenize('1')\n",
       "    >>> predicate(token)\n",
       "    True</pre>\n",
       "<h3>caseless <a name=\"predicates.caseless\"><a href=\"#predicates.caseless\">#</a></h3>\n",
       "<br/><pre>    a.lower() == b.lower()\n",
       "\n",
       "    >>> predicate = caseless('Рано')\n",
       "    >>> token, = tokenize('РАНО')\n",
       "    >>> predicate(token)\n",
       "    True</pre>\n",
       "<h3>in_ <a name=\"predicates.in_\"><a href=\"#predicates.in_\">#</a></h3>\n",
       "<br/><pre>    a in b\n",
       "\n",
       "    >>> predicate = in_({'S', 'M', 'L'})\n",
       "    >>> a, b = tokenize('S 1')\n",
       "    >>> predicate(a)\n",
       "    True\n",
       "    >>> predicate(b)\n",
       "    False</pre>\n",
       "<h3>in_caseless <a name=\"predicates.in_caseless\"><a href=\"#predicates.in_caseless\">#</a></h3>\n",
       "<br/><pre>    a.lower() in b\n",
       "\n",
       "    >>> predicate = in_caseless({'S', 'M', 'L'})\n",
       "    >>> a, b = tokenize('S m')\n",
       "    >>> predicate(a)\n",
       "    True\n",
       "    >>> predicate(b)\n",
       "    True</pre>\n",
       "<h3>gte <a name=\"predicates.gte\"><a href=\"#predicates.gte\">#</a></h3>\n",
       "<br/><pre>    a >= b\n",
       "\n",
       "    >>> predicate = gte(4)\n",
       "    >>> a, b, c = tokenize('3 5 C')\n",
       "    >>> predicate(a)\n",
       "    False\n",
       "    >>> predicate(b)\n",
       "    True\n",
       "    >>> predicate(c)\n",
       "    False</pre>\n",
       "<h3>lte <a name=\"predicates.lte\"><a href=\"#predicates.lte\">#</a></h3>\n",
       "<br/><pre>    a <= b\n",
       "\n",
       "    >>> predicate = lte(4)\n",
       "    >>> a, b, c = tokenize('3 5 C')\n",
       "    >>> predicate(a)\n",
       "    True\n",
       "    >>> predicate(b)\n",
       "    False\n",
       "    >>> predicate(c)\n",
       "    False</pre>\n",
       "<h3>length_eq <a name=\"predicates.length_eq\"><a href=\"#predicates.length_eq\">#</a></h3>\n",
       "<br/><pre>    len(a) == b\n",
       "\n",
       "    >>> predicate = length_eq(3)\n",
       "    >>> a, b = tokenize('XXX 123')\n",
       "    >>> predicate(a)\n",
       "    True\n",
       "    >>> predicate(b)\n",
       "    True</pre>\n",
       "<h3>normalized <a name=\"predicates.normalized\"><a href=\"#predicates.normalized\">#</a></h3>\n",
       "<br/><pre>    Нормальная форма слова == value\n",
       "\n",
       "    >>> a = activate(normalized('сталь'))\n",
       "    >>> b = activate(normalized('стать'))\n",
       "    >>> token, = tokenize('стали')\n",
       "    >>> a(token)\n",
       "    True\n",
       "    >>> b(token)\n",
       "    True</pre>\n",
       "<h3>dictionary <a name=\"predicates.dictionary\"><a href=\"#predicates.dictionary\">#</a></h3>\n",
       "<br/><pre>    Нормальная форма слова in value\n",
       "\n",
       "    >>> predicate = activate(dictionary({'учитель', 'врач'}))\n",
       "    >>> a, b = tokenize('учителя врачи')\n",
       "    >>> predicate(a)\n",
       "    True\n",
       "    >>> predicate(b)\n",
       "    True</pre>\n",
       "<h3>gram <a name=\"predicates.gram\"><a href=\"#predicates.gram\">#</a></h3>\n",
       "<br/><pre>    value есть среди граммем слова\n",
       "\n",
       "    >>> a = activate(gram('NOUN'))\n",
       "    >>> b = activate(gram('VERB'))\n",
       "    >>> token, = tokenize('стали')\n",
       "    >>> a(token)\n",
       "    True\n",
       "    >>> b(token)\n",
       "    True</pre>\n",
       "<h3>type <a name=\"predicates.type\"><a href=\"#predicates.type\">#</a></h3>\n",
       "<br/><pre>    Тип токена равен value\n",
       "\n",
       "    >>> predicate = activate(type('INT'))\n",
       "    >>> a, b = tokenize('3 раза')\n",
       "    >>> predicate(a)\n",
       "    True\n",
       "    >>> predicate(b)\n",
       "    False</pre>\n",
       "<h3>tag <a name=\"predicates.tag\"><a href=\"#predicates.tag\">#</a></h3>\n",
       "<br/><pre>    Тег токена равен value</pre>\n",
       "<h3>custom <a name=\"predicates.custom\"><a href=\"#predicates.custom\">#</a></h3>\n",
       "<br/><pre>    function в качестве предиката\n",
       "\n",
       "    >>> from math import log\n",
       "    >>> f = lambda x: int(log(int(x), 10)) == 2\n",
       "    >>> predicate = activate(custom(f, types=INT))\n",
       "    >>> a, b = tokenize('12 123')\n",
       "    >>> predicate(a)\n",
       "    False\n",
       "    >>> predicate(b)\n",
       "    True</pre>\n",
       "<h3>true <a name=\"predicates.true\"><a href=\"#predicates.true\">#</a></h3>\n",
       "<br/><pre>    Всегда возвращает True\n",
       "\n",
       "    >>> predicate = true()\n",
       "    >>> predicate(False)\n",
       "    True</pre>\n",
       "<h3>is_lower <a name=\"predicates.is_lower\"><a href=\"#predicates.is_lower\">#</a></h3>\n",
       "<br/><pre>    str.islower\n",
       "\n",
       "    >>> predicate = is_lower()\n",
       "    >>> a, b = tokenize('xxx Xxx')\n",
       "    >>> predicate(a)\n",
       "    True\n",
       "    >>> predicate(b)\n",
       "    False</pre>\n",
       "<h3>is_upper <a name=\"predicates.is_upper\"><a href=\"#predicates.is_upper\">#</a></h3>\n",
       "<br/><pre>    str.isupper\n",
       "\n",
       "    >>> predicate = is_upper()\n",
       "    >>> a, b = tokenize('XXX xxx')\n",
       "    >>> predicate(a)\n",
       "    True\n",
       "    >>> predicate(b)\n",
       "    False</pre>\n",
       "<h3>is_title <a name=\"predicates.is_title\"><a href=\"#predicates.is_title\">#</a></h3>\n",
       "<br/><pre>    str.istitle\n",
       "\n",
       "    >>> predicate = is_title()\n",
       "    >>> a, b = tokenize('XXX Xxx')\n",
       "    >>> predicate(a)\n",
       "    False\n",
       "    >>> predicate(b)\n",
       "    True</pre>\n",
       "<h3>is_capitalized <a name=\"predicates.is_capitalized\"><a href=\"#predicates.is_capitalized\">#</a></h3>\n",
       "<br/><pre>    Слово написано с большой буквы\n",
       "\n",
       "    >>> predicate = is_capitalized()\n",
       "    >>> a, b, c = tokenize('Xxx XXX xxX')\n",
       "    >>> predicate(a)\n",
       "    True\n",
       "    >>> predicate(b)\n",
       "    True\n",
       "    >>> predicate(c)\n",
       "    False</pre>\n",
       "<h3>is_single <a name=\"predicates.is_single\"><a href=\"#predicates.is_single\">#</a></h3>\n",
       "<br/><pre>    Слово в единственном числе\n",
       "\n",
       "    >>> predicate = is_single()\n",
       "    >>> token, = tokenize('слово')\n",
       "    >>> predicate(token)\n",
       "    True</pre>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from IPython.display import HTML\n",
    "\n",
    "from yargy.predicates import bank\n",
    "\n",
    "def html():\n",
    "    for name in bank.__all__:\n",
    "        yield f'<h3>{name} <a name=\"predicates.{name}\"><a href=\"#predicates.{name}\">#</a></h3>'\n",
    "        doc = getattr(bank, name).__doc__.strip()\n",
    "        yield f'<br/><pre>    {doc}</pre>'\n",
    "\n",
    "HTML('\\n'.join(html()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Интерпретация"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Объект-результат интерпретации описывает конструктор `fact`. `attribute` задаёт значение поля по-умолчанию. Например, в `Date` по-умолчанию год будет равен 2017:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Date(\n",
       "    year='2016',\n",
       "    month='июля',\n",
       "    day='18'\n",
       ")"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": [
       "Date(\n",
       "    year=2017,\n",
       "    month='марта',\n",
       "    day='15'\n",
       ")"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from IPython.display import display\n",
    "\n",
    "from yargy import Parser, rule, and_, or_\n",
    "from yargy.interpretation import fact, attribute\n",
    "from yargy.predicates import dictionary, gte, lte\n",
    "\n",
    "\n",
    "Date = fact(\n",
    "    'Date',\n",
    "    [attribute('year', 2017), 'month', 'day']\n",
    ")\n",
    "\n",
    "\n",
    "MONTHS = {\n",
    "    'январь',\n",
    "    'февраль',\n",
    "    'март',\n",
    "    'апрель',\n",
    "    'мая',\n",
    "    'июнь',\n",
    "    'июль',\n",
    "    'август',\n",
    "    'сентябрь',\n",
    "    'октябрь',\n",
    "    'ноябрь',\n",
    "    'декабрь'\n",
    "}\n",
    "\n",
    "\n",
    "MONTH_NAME = dictionary(MONTHS)\n",
    "DAY = and_(\n",
    "    gte(1),\n",
    "    lte(31)\n",
    ")\n",
    "YEAR = and_(\n",
    "    gte(1900),\n",
    "    lte(2100)\n",
    ")\n",
    "DATE = rule(\n",
    "    DAY.interpretation(\n",
    "        Date.day\n",
    "    ),\n",
    "    MONTH_NAME.interpretation(\n",
    "        Date.month\n",
    "    ),\n",
    "    YEAR.interpretation(\n",
    "        Date.year\n",
    "    ).optional()\n",
    ").interpretation(\n",
    "    Date\n",
    ")\n",
    "\n",
    "\n",
    "text = '''18 июля 2016\n",
    "15 марта\n",
    "'''\n",
    "parser = Parser(DATE)\n",
    "for line in text.splitlines():\n",
    "    match = parser.match(line)\n",
    "    display(match.fact)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Для дат деревья разбора выглядят просто: вершина-конструктор и несколько детей-атрибутов:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/svg+xml": [
       "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n",
       "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
       " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
       "<!-- Generated by graphviz version 6.0.2 (20221011.1828)\n",
       " -->\n",
       "<!-- Title: G Pages: 1 -->\n",
       "<svg width=\"179pt\" height=\"108pt\"\n",
       " viewBox=\"0.00 0.00 178.50 108.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
       "<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 104)\">\n",
       "<title>G</title>\n",
       "<polygon fill=\"white\" stroke=\"none\" points=\"-4,4 -4,-104 174.5,-104 174.5,4 -4,4\"/>\n",
       "<!-- 0 -->\n",
       "<g id=\"node1\" class=\"node\">\n",
       "<title>0</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"110,-100 72,-100 72,-81 110,-81 110,-100\"/>\n",
       "<text text-anchor=\"middle\" x=\"91\" y=\"-88\" font-family=\"sans\" font-size=\"10.00\">Date</text>\n",
       "</g>\n",
       "<!-- 1 -->\n",
       "<g id=\"node2\" class=\"node\">\n",
       "<title>1</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"56,-73 0,-73 0,-54 56,-54 56,-73\"/>\n",
       "<text text-anchor=\"middle\" x=\"28\" y=\"-61\" font-family=\"sans\" font-size=\"10.00\">Date.day</text>\n",
       "</g>\n",
       "<!-- 0&#45;&gt;1 -->\n",
       "<g id=\"edge1\" class=\"edge\">\n",
       "<title>0&#45;&gt;1</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M71.76,-81.86C65.78,-79.49 59.12,-76.85 52.82,-74.35\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"52.95,-73.27 49.77,-73.14 52.17,-75.22 52.95,-73.27\"/>\n",
       "</g>\n",
       "<!-- 3 -->\n",
       "<g id=\"node4\" class=\"node\">\n",
       "<title>3</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"125,-73 57,-73 57,-54 125,-54 125,-73\"/>\n",
       "<text text-anchor=\"middle\" x=\"91\" y=\"-61\" font-family=\"sans\" font-size=\"10.00\">Date.month</text>\n",
       "</g>\n",
       "<!-- 0&#45;&gt;3 -->\n",
       "<g id=\"edge2\" class=\"edge\">\n",
       "<title>0&#45;&gt;3</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M91,-80.76C91,-79.36 91,-77.9 91,-76.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"92.05,-76.11 91,-73.11 89.95,-76.11 92.05,-76.11\"/>\n",
       "</g>\n",
       "<!-- 5 -->\n",
       "<g id=\"node6\" class=\"node\">\n",
       "<title>5</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"155.5,-73 126.5,-73 126.5,-54 155.5,-54 155.5,-73\"/>\n",
       "<text text-anchor=\"middle\" x=\"141\" y=\"-61\" font-family=\"sans\" font-size=\"10.00\">R3</text>\n",
       "</g>\n",
       "<!-- 0&#45;&gt;5 -->\n",
       "<g id=\"edge3\" class=\"edge\">\n",
       "<title>0&#45;&gt;5</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M108.21,-80.9C113.16,-78.42 118.58,-75.71 123.55,-73.23\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"124.05,-74.15 126.26,-71.87 123.11,-72.27 124.05,-74.15\"/>\n",
       "</g>\n",
       "<!-- 2 -->\n",
       "<g id=\"node3\" class=\"node\">\n",
       "<title>2</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"41.5,-46 14.5,-46 14.5,-27 41.5,-27 41.5,-46\"/>\n",
       "<text text-anchor=\"middle\" x=\"28\" y=\"-34\" font-family=\"sans\" font-size=\"10.00\">18</text>\n",
       "</g>\n",
       "<!-- 1&#45;&gt;2 -->\n",
       "<g id=\"edge4\" class=\"edge\">\n",
       "<title>1&#45;&gt;2</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M28,-53.76C28,-52.36 28,-50.9 28,-49.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"29.05,-49.11 28,-46.11 26.95,-49.11 29.05,-49.11\"/>\n",
       "</g>\n",
       "<!-- 4 -->\n",
       "<g id=\"node5\" class=\"node\">\n",
       "<title>4</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"110.5,-46 69.5,-46 69.5,-27 110.5,-27 110.5,-46\"/>\n",
       "<text text-anchor=\"middle\" x=\"90\" y=\"-34\" font-family=\"sans\" font-size=\"10.00\">июля</text>\n",
       "</g>\n",
       "<!-- 3&#45;&gt;4 -->\n",
       "<g id=\"edge5\" class=\"edge\">\n",
       "<title>3&#45;&gt;4</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M90.65,-53.76C90.59,-52.36 90.54,-50.9 90.48,-49.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"91.51,-49.07 90.34,-46.11 89.42,-49.15 91.51,-49.07\"/>\n",
       "</g>\n",
       "<!-- 6 -->\n",
       "<g id=\"node7\" class=\"node\">\n",
       "<title>6</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"170.5,-46 111.5,-46 111.5,-27 170.5,-27 170.5,-46\"/>\n",
       "<text text-anchor=\"middle\" x=\"141\" y=\"-34\" font-family=\"sans\" font-size=\"10.00\">Date.year</text>\n",
       "</g>\n",
       "<!-- 5&#45;&gt;6 -->\n",
       "<g id=\"edge6\" class=\"edge\">\n",
       "<title>5&#45;&gt;6</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M141,-53.76C141,-52.36 141,-50.9 141,-49.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"142.05,-49.11 141,-46.11 139.95,-49.11 142.05,-49.11\"/>\n",
       "</g>\n",
       "<!-- 7 -->\n",
       "<g id=\"node8\" class=\"node\">\n",
       "<title>7</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"160,-19 122,-19 122,0 160,0 160,-19\"/>\n",
       "<text text-anchor=\"middle\" x=\"141\" y=\"-7\" font-family=\"sans\" font-size=\"10.00\">2016</text>\n",
       "</g>\n",
       "<!-- 6&#45;&gt;7 -->\n",
       "<g id=\"edge7\" class=\"edge\">\n",
       "<title>6&#45;&gt;7</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M141,-26.76C141,-25.36 141,-23.9 141,-22.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"142.05,-22.11 141,-19.11 139.95,-22.11 142.05,-22.11\"/>\n",
       "</g>\n",
       "</g>\n",
       "</svg>\n"
      ],
      "text/plain": [
       "Graph(nodes=[...], edges=[...])"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/svg+xml": [
       "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n",
       "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
       " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
       "<!-- Generated by graphviz version 6.0.2 (20221011.1828)\n",
       " -->\n",
       "<!-- Title: G Pages: 1 -->\n",
       "<svg width=\"133pt\" height=\"81pt\"\n",
       " viewBox=\"0.00 0.00 133.00 81.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
       "<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 77)\">\n",
       "<title>G</title>\n",
       "<polygon fill=\"white\" stroke=\"none\" points=\"-4,4 -4,-77 129,-77 129,4 -4,4\"/>\n",
       "<!-- 0 -->\n",
       "<g id=\"node1\" class=\"node\">\n",
       "<title>0</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"78,-73 40,-73 40,-54 78,-54 78,-73\"/>\n",
       "<text text-anchor=\"middle\" x=\"59\" y=\"-61\" font-family=\"sans\" font-size=\"10.00\">Date</text>\n",
       "</g>\n",
       "<!-- 1 -->\n",
       "<g id=\"node2\" class=\"node\">\n",
       "<title>1</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"56,-46 0,-46 0,-27 56,-27 56,-46\"/>\n",
       "<text text-anchor=\"middle\" x=\"28\" y=\"-34\" font-family=\"sans\" font-size=\"10.00\">Date.day</text>\n",
       "</g>\n",
       "<!-- 0&#45;&gt;1 -->\n",
       "<g id=\"edge1\" class=\"edge\">\n",
       "<title>0&#45;&gt;1</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M48.16,-53.76C45.85,-51.9 43.4,-49.92 41.02,-48\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"41.67,-47.18 38.68,-46.11 40.36,-48.81 41.67,-47.18\"/>\n",
       "</g>\n",
       "<!-- 3 -->\n",
       "<g id=\"node4\" class=\"node\">\n",
       "<title>3</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"125,-46 57,-46 57,-27 125,-27 125,-46\"/>\n",
       "<text text-anchor=\"middle\" x=\"91\" y=\"-34\" font-family=\"sans\" font-size=\"10.00\">Date.month</text>\n",
       "</g>\n",
       "<!-- 0&#45;&gt;3 -->\n",
       "<g id=\"edge2\" class=\"edge\">\n",
       "<title>0&#45;&gt;3</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M70.19,-53.76C72.57,-51.9 75.11,-49.92 77.56,-48\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"78.26,-48.79 79.98,-46.11 76.97,-47.13 78.26,-48.79\"/>\n",
       "</g>\n",
       "<!-- 2 -->\n",
       "<g id=\"node3\" class=\"node\">\n",
       "<title>2</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"41.5,-19 14.5,-19 14.5,0 41.5,0 41.5,-19\"/>\n",
       "<text text-anchor=\"middle\" x=\"28\" y=\"-7\" font-family=\"sans\" font-size=\"10.00\">15</text>\n",
       "</g>\n",
       "<!-- 1&#45;&gt;2 -->\n",
       "<g id=\"edge3\" class=\"edge\">\n",
       "<title>1&#45;&gt;2</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M28,-26.76C28,-25.36 28,-23.9 28,-22.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"29.05,-22.11 28,-19.11 26.95,-22.11 29.05,-22.11\"/>\n",
       "</g>\n",
       "<!-- 4 -->\n",
       "<g id=\"node5\" class=\"node\">\n",
       "<title>4</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"113,-19 69,-19 69,0 113,0 113,-19\"/>\n",
       "<text text-anchor=\"middle\" x=\"91\" y=\"-7\" font-family=\"sans\" font-size=\"10.00\">марта</text>\n",
       "</g>\n",
       "<!-- 3&#45;&gt;4 -->\n",
       "<g id=\"edge4\" class=\"edge\">\n",
       "<title>3&#45;&gt;4</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M91,-26.76C91,-25.36 91,-23.9 91,-22.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"92.05,-22.11 91,-19.11 89.95,-22.11 92.05,-22.11\"/>\n",
       "</g>\n",
       "</g>\n",
       "</svg>\n"
      ],
      "text/plain": [
       "Graph(nodes=[...], edges=[...])"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "parser = Parser(DATE)\n",
    "for line in text.splitlines():\n",
    "    match = parser.match(line)\n",
    "    display(match.tree.as_dot)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Как будет себя вести алгоритм интерпретации, когда ребёнок конструктора не атрибут, а другой конструктор? Или когда ребёнок атрибута другой атрибут? Или когда под конструктором или атрибутом не одна, а несколько вершин с токенами? Пойдём от простого к сложному. Когда под вершиной-атрибутом несколько токенов, они объединяются:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/svg+xml": [
       "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n",
       "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
       " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
       "<!-- Generated by graphviz version 6.0.2 (20221011.1828)\n",
       " -->\n",
       "<!-- Title: G Pages: 1 -->\n",
       "<svg width=\"165pt\" height=\"81pt\"\n",
       " viewBox=\"0.00 0.00 165.00 81.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
       "<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 77)\">\n",
       "<title>G</title>\n",
       "<polygon fill=\"white\" stroke=\"none\" points=\"-4,4 -4,-77 161,-77 161,4 -4,4\"/>\n",
       "<!-- 0 -->\n",
       "<g id=\"node1\" class=\"node\">\n",
       "<title>0</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"97.5,-73 51.5,-73 51.5,-54 97.5,-54 97.5,-73\"/>\n",
       "<text text-anchor=\"middle\" x=\"74.5\" y=\"-61\" font-family=\"sans\" font-size=\"10.00\">Money</text>\n",
       "</g>\n",
       "<!-- 1 -->\n",
       "<g id=\"node2\" class=\"node\">\n",
       "<title>1</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"71,-46 0,-46 0,-27 71,-27 71,-46\"/>\n",
       "<text text-anchor=\"middle\" x=\"35.5\" y=\"-34\" font-family=\"sans\" font-size=\"10.00\">Money.value</text>\n",
       "</g>\n",
       "<!-- 0&#45;&gt;1 -->\n",
       "<g id=\"edge1\" class=\"edge\">\n",
       "<title>0&#45;&gt;1</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M60.86,-53.76C57.84,-51.82 54.61,-49.75 51.51,-47.76\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"52.03,-46.85 48.94,-46.11 50.89,-48.62 52.03,-46.85\"/>\n",
       "</g>\n",
       "<!-- 4 -->\n",
       "<g id=\"node5\" class=\"node\">\n",
       "<title>4</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"157,-46 72,-46 72,-27 157,-27 157,-46\"/>\n",
       "<text text-anchor=\"middle\" x=\"114.5\" y=\"-34\" font-family=\"sans\" font-size=\"10.00\">Money.currency</text>\n",
       "</g>\n",
       "<!-- 0&#45;&gt;4 -->\n",
       "<g id=\"edge2\" class=\"edge\">\n",
       "<title>0&#45;&gt;4</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M88.49,-53.76C91.59,-51.82 94.9,-49.75 98.08,-47.76\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"98.73,-48.59 100.72,-46.11 97.62,-46.81 98.73,-48.59\"/>\n",
       "</g>\n",
       "<!-- 2 -->\n",
       "<g id=\"node3\" class=\"node\">\n",
       "<title>2</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"29.5,-19 7.5,-19 7.5,0 29.5,0 29.5,-19\"/>\n",
       "<text text-anchor=\"middle\" x=\"18.5\" y=\"-7\" font-family=\"sans\" font-size=\"10.00\">5</text>\n",
       "</g>\n",
       "<!-- 1&#45;&gt;2 -->\n",
       "<g id=\"edge3\" class=\"edge\">\n",
       "<title>1&#45;&gt;2</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M29.55,-26.76C28.5,-25.21 27.39,-23.57 26.29,-21.96\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"26.91,-21 24.36,-19.11 25.18,-22.18 26.91,-21\"/>\n",
       "</g>\n",
       "<!-- 3 -->\n",
       "<g id=\"node4\" class=\"node\">\n",
       "<title>3</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"76,-19 31,-19 31,0 76,0 76,-19\"/>\n",
       "<text text-anchor=\"middle\" x=\"53.5\" y=\"-7\" font-family=\"sans\" font-size=\"10.00\">тысяч</text>\n",
       "</g>\n",
       "<!-- 1&#45;&gt;3 -->\n",
       "<g id=\"edge4\" class=\"edge\">\n",
       "<title>1&#45;&gt;3</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M41.8,-26.76C42.91,-25.21 44.09,-23.57 45.25,-21.96\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"46.4,-22.16 47.3,-19.11 44.69,-20.93 46.4,-22.16\"/>\n",
       "</g>\n",
       "<!-- 5 -->\n",
       "<g id=\"node6\" class=\"node\">\n",
       "<title>5</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"125.5,-19 103.5,-19 103.5,0 125.5,0 125.5,-19\"/>\n",
       "<text text-anchor=\"middle\" x=\"114.5\" y=\"-7\" font-family=\"sans\" font-size=\"10.00\">$</text>\n",
       "</g>\n",
       "<!-- 4&#45;&gt;5 -->\n",
       "<g id=\"edge5\" class=\"edge\">\n",
       "<title>4&#45;&gt;5</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M114.5,-26.76C114.5,-25.36 114.5,-23.9 114.5,-22.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"115.55,-22.11 114.5,-19.11 113.45,-22.11 115.55,-22.11\"/>\n",
       "</g>\n",
       "</g>\n",
       "</svg>\n"
      ],
      "text/plain": [
       "Graph(nodes=[...], edges=[...])"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from yargy.predicates import eq, type, dictionary\n",
    "\n",
    "\n",
    "Money = fact(\n",
    "    'Money',\n",
    "    ['value', 'currency']\n",
    ")\n",
    "MONEY = rule(\n",
    "    rule(\n",
    "        type('INT'),\n",
    "        dictionary({\n",
    "            'тысяча',\n",
    "            'миллион'\n",
    "        })\n",
    "    ).interpretation(\n",
    "        Money.value\n",
    "    ),\n",
    "    eq('$').interpretation(\n",
    "        Money.currency\n",
    "    )\n",
    ").interpretation(\n",
    "    Money\n",
    ")\n",
    "\n",
    "parser = Parser(MONEY)\n",
    "match = parser.match('5 тысяч$')\n",
    "match.tree.as_dot"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В `Money.value` два слова:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Money(\n",
       "    value='5 тысяч',\n",
       "    currency='$'\n",
       ")"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "match.fact"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Когда под вершиной-атрибутом смесь из токенов и вершин-конструктов, интерпретация кидает `TypeError`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/svg+xml": [
       "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n",
       "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
       " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
       "<!-- Generated by graphviz version 6.0.2 (20221011.1828)\n",
       " -->\n",
       "<!-- Title: G Pages: 1 -->\n",
       "<svg width=\"60pt\" height=\"135pt\"\n",
       " viewBox=\"0.00 0.00 59.50 135.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
       "<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 131)\">\n",
       "<title>G</title>\n",
       "<polygon fill=\"white\" stroke=\"none\" points=\"-4,4 -4,-131 55.5,-131 55.5,4 -4,4\"/>\n",
       "<!-- 0 -->\n",
       "<g id=\"node1\" class=\"node\">\n",
       "<title>0</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"35,-127 12,-127 12,-108 35,-108 35,-127\"/>\n",
       "<text text-anchor=\"middle\" x=\"23.5\" y=\"-115\" font-family=\"sans\" font-size=\"10.00\">A</text>\n",
       "</g>\n",
       "<!-- 1 -->\n",
       "<g id=\"node2\" class=\"node\">\n",
       "<title>1</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"39.5,-100 7.5,-100 7.5,-81 39.5,-81 39.5,-100\"/>\n",
       "<text text-anchor=\"middle\" x=\"23.5\" y=\"-88\" font-family=\"sans\" font-size=\"10.00\">A.x</text>\n",
       "</g>\n",
       "<!-- 0&#45;&gt;1 -->\n",
       "<g id=\"edge1\" class=\"edge\">\n",
       "<title>0&#45;&gt;1</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M23.5,-107.76C23.5,-106.36 23.5,-104.9 23.5,-103.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"24.55,-103.11 23.5,-100.11 22.45,-103.11 24.55,-103.11\"/>\n",
       "</g>\n",
       "<!-- 2 -->\n",
       "<g id=\"node3\" class=\"node\">\n",
       "<title>2</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"23,-73 0,-73 0,-54 23,-54 23,-73\"/>\n",
       "<text text-anchor=\"middle\" x=\"11.5\" y=\"-61\" font-family=\"sans\" font-size=\"10.00\">X</text>\n",
       "</g>\n",
       "<!-- 1&#45;&gt;2 -->\n",
       "<g id=\"edge2\" class=\"edge\">\n",
       "<title>1&#45;&gt;2</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M19.3,-80.76C18.56,-79.21 17.78,-77.57 17,-75.96\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"17.88,-75.36 15.63,-73.11 15.99,-76.27 17.88,-75.36\"/>\n",
       "</g>\n",
       "<!-- 3 -->\n",
       "<g id=\"node4\" class=\"node\">\n",
       "<title>3</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"47,-73 24,-73 24,-54 47,-54 47,-73\"/>\n",
       "<text text-anchor=\"middle\" x=\"35.5\" y=\"-61\" font-family=\"sans\" font-size=\"10.00\">B</text>\n",
       "</g>\n",
       "<!-- 1&#45;&gt;3 -->\n",
       "<g id=\"edge3\" class=\"edge\">\n",
       "<title>1&#45;&gt;3</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M27.7,-80.76C28.44,-79.21 29.22,-77.57 30,-75.96\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"31.01,-76.27 31.37,-73.11 29.12,-75.36 31.01,-76.27\"/>\n",
       "</g>\n",
       "<!-- 4 -->\n",
       "<g id=\"node5\" class=\"node\">\n",
       "<title>4</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"51.5,-46 19.5,-46 19.5,-27 51.5,-27 51.5,-46\"/>\n",
       "<text text-anchor=\"middle\" x=\"35.5\" y=\"-34\" font-family=\"sans\" font-size=\"10.00\">B.y</text>\n",
       "</g>\n",
       "<!-- 3&#45;&gt;4 -->\n",
       "<g id=\"edge4\" class=\"edge\">\n",
       "<title>3&#45;&gt;4</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M35.5,-53.76C35.5,-52.36 35.5,-50.9 35.5,-49.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"36.55,-49.11 35.5,-46.11 34.45,-49.11 36.55,-49.11\"/>\n",
       "</g>\n",
       "<!-- 5 -->\n",
       "<g id=\"node6\" class=\"node\">\n",
       "<title>5</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"47,-19 24,-19 24,0 47,0 47,-19\"/>\n",
       "<text text-anchor=\"middle\" x=\"35.5\" y=\"-7\" font-family=\"sans\" font-size=\"10.00\">Y</text>\n",
       "</g>\n",
       "<!-- 4&#45;&gt;5 -->\n",
       "<g id=\"edge5\" class=\"edge\">\n",
       "<title>4&#45;&gt;5</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M35.5,-26.76C35.5,-25.36 35.5,-23.9 35.5,-22.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"36.55,-22.11 35.5,-19.11 34.45,-22.11 36.55,-22.11\"/>\n",
       "</g>\n",
       "</g>\n",
       "</svg>\n"
      ],
      "text/plain": [
       "Graph(nodes=[...], edges=[...])"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from yargy.predicates import true\n",
    "\n",
    "\n",
    "A = fact(\n",
    "    'A',\n",
    "    ['x']\n",
    ")\n",
    "B = fact(\n",
    "    'B',\n",
    "    ['y']\n",
    ")\n",
    "RULE = rule(\n",
    "    true(),\n",
    "    true().interpretation(\n",
    "        B.y\n",
    "    ).interpretation(\n",
    "        B\n",
    "    )\n",
    ").interpretation(\n",
    "    A.x\n",
    ").interpretation(\n",
    "    A\n",
    ")\n",
    "\n",
    "parser = Parser(RULE)\n",
    "match = parser.match('X Y')\n",
    "match.tree.as_dot"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "# match.fact Будет TypeError"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Если под вершиной-атрибутом другая вершина-атрибут, нижняя просто исчезает:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/svg+xml": [
       "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n",
       "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
       " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
       "<!-- Generated by graphviz version 6.0.2 (20221011.1828)\n",
       " -->\n",
       "<!-- Title: G Pages: 1 -->\n",
       "<svg width=\"40pt\" height=\"108pt\"\n",
       " viewBox=\"0.00 0.00 40.00 108.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
       "<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 104)\">\n",
       "<title>G</title>\n",
       "<polygon fill=\"white\" stroke=\"none\" points=\"-4,4 -4,-104 36,-104 36,4 -4,4\"/>\n",
       "<!-- 0 -->\n",
       "<g id=\"node1\" class=\"node\">\n",
       "<title>0</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"27.5,-100 4.5,-100 4.5,-81 27.5,-81 27.5,-100\"/>\n",
       "<text text-anchor=\"middle\" x=\"16\" y=\"-88\" font-family=\"sans\" font-size=\"10.00\">A</text>\n",
       "</g>\n",
       "<!-- 1 -->\n",
       "<g id=\"node2\" class=\"node\">\n",
       "<title>1</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"32,-73 0,-73 0,-54 32,-54 32,-73\"/>\n",
       "<text text-anchor=\"middle\" x=\"16\" y=\"-61\" font-family=\"sans\" font-size=\"10.00\">A.y</text>\n",
       "</g>\n",
       "<!-- 0&#45;&gt;1 -->\n",
       "<g id=\"edge1\" class=\"edge\">\n",
       "<title>0&#45;&gt;1</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M16,-80.76C16,-79.36 16,-77.9 16,-76.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"17.05,-76.11 16,-73.11 14.95,-76.11 17.05,-76.11\"/>\n",
       "</g>\n",
       "<!-- 2 -->\n",
       "<g id=\"node3\" class=\"node\">\n",
       "<title>2</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"32,-46 0,-46 0,-27 32,-27 32,-46\"/>\n",
       "<text text-anchor=\"middle\" x=\"16\" y=\"-34\" font-family=\"sans\" font-size=\"10.00\">A.x</text>\n",
       "</g>\n",
       "<!-- 1&#45;&gt;2 -->\n",
       "<g id=\"edge2\" class=\"edge\">\n",
       "<title>1&#45;&gt;2</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M16,-53.76C16,-52.36 16,-50.9 16,-49.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"17.05,-49.11 16,-46.11 14.95,-49.11 17.05,-49.11\"/>\n",
       "</g>\n",
       "<!-- 3 -->\n",
       "<g id=\"node4\" class=\"node\">\n",
       "<title>3</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"27.5,-19 4.5,-19 4.5,0 27.5,0 27.5,-19\"/>\n",
       "<text text-anchor=\"middle\" x=\"16\" y=\"-7\" font-family=\"sans\" font-size=\"10.00\">X</text>\n",
       "</g>\n",
       "<!-- 2&#45;&gt;3 -->\n",
       "<g id=\"edge3\" class=\"edge\">\n",
       "<title>2&#45;&gt;3</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M16,-26.76C16,-25.36 16,-23.9 16,-22.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"17.05,-22.11 16,-19.11 14.95,-22.11 17.05,-22.11\"/>\n",
       "</g>\n",
       "</g>\n",
       "</svg>\n"
      ],
      "text/plain": [
       "Graph(nodes=[...], edges=[...])"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from yargy.predicates import true\n",
    "\n",
    "\n",
    "A = fact(\n",
    "    'A',\n",
    "    ['x', 'y']\n",
    ")\n",
    "RULE = true().interpretation(\n",
    "    A.x\n",
    ").interpretation(\n",
    "    A.y\n",
    ").interpretation(A)\n",
    "\n",
    "parser = Parser(RULE)\n",
    "match = parser.match('X')\n",
    "match.tree.as_dot"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\"X\" попадёт в `A.y`, не в `A.x`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "A(\n",
       "    x=None,\n",
       "    y='X'\n",
       ")"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "match.fact"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Что если под вершиной-конструктом несколько одинаковых вершин-атрибутов? Самый правый атрибут перезаписывает все остальные:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/svg+xml": [
       "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n",
       "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
       " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
       "<!-- Generated by graphviz version 6.0.2 (20221011.1828)\n",
       " -->\n",
       "<!-- Title: G Pages: 1 -->\n",
       "<svg width=\"102pt\" height=\"162pt\"\n",
       " viewBox=\"0.00 0.00 102.00 162.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
       "<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 158)\">\n",
       "<title>G</title>\n",
       "<polygon fill=\"white\" stroke=\"none\" points=\"-4,4 -4,-158 98,-158 98,4 -4,4\"/>\n",
       "<!-- 0 -->\n",
       "<g id=\"node1\" class=\"node\">\n",
       "<title>0</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"43.5,-154 20.5,-154 20.5,-135 43.5,-135 43.5,-154\"/>\n",
       "<text text-anchor=\"middle\" x=\"32\" y=\"-142\" font-family=\"sans\" font-size=\"10.00\">A</text>\n",
       "</g>\n",
       "<!-- 1 -->\n",
       "<g id=\"node2\" class=\"node\">\n",
       "<title>1</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"46.5,-127 17.5,-127 17.5,-108 46.5,-108 46.5,-127\"/>\n",
       "<text text-anchor=\"middle\" x=\"32\" y=\"-115\" font-family=\"sans\" font-size=\"10.00\">R1</text>\n",
       "</g>\n",
       "<!-- 0&#45;&gt;1 -->\n",
       "<g id=\"edge1\" class=\"edge\">\n",
       "<title>0&#45;&gt;1</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M32,-134.76C32,-133.36 32,-131.9 32,-130.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"33.05,-130.11 32,-127.11 30.95,-130.11 33.05,-130.11\"/>\n",
       "</g>\n",
       "<!-- 2 -->\n",
       "<g id=\"node3\" class=\"node\">\n",
       "<title>2</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"32,-100 0,-100 0,-81 32,-81 32,-100\"/>\n",
       "<text text-anchor=\"middle\" x=\"16\" y=\"-88\" font-family=\"sans\" font-size=\"10.00\">A.x</text>\n",
       "</g>\n",
       "<!-- 1&#45;&gt;2 -->\n",
       "<g id=\"edge2\" class=\"edge\">\n",
       "<title>1&#45;&gt;2</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M26.4,-107.76C25.41,-106.21 24.37,-104.57 23.34,-102.96\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"24.01,-102.07 21.51,-100.11 22.24,-103.21 24.01,-102.07\"/>\n",
       "</g>\n",
       "<!-- 4 -->\n",
       "<g id=\"node5\" class=\"node\">\n",
       "<title>4</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"62.5,-100 33.5,-100 33.5,-81 62.5,-81 62.5,-100\"/>\n",
       "<text text-anchor=\"middle\" x=\"48\" y=\"-88\" font-family=\"sans\" font-size=\"10.00\">R1</text>\n",
       "</g>\n",
       "<!-- 1&#45;&gt;4 -->\n",
       "<g id=\"edge3\" class=\"edge\">\n",
       "<title>1&#45;&gt;4</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M37.6,-107.76C38.59,-106.21 39.63,-104.57 40.66,-102.96\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"41.76,-103.21 42.49,-100.11 39.99,-102.07 41.76,-103.21\"/>\n",
       "</g>\n",
       "<!-- 3 -->\n",
       "<g id=\"node4\" class=\"node\">\n",
       "<title>3</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"27,-73 5,-73 5,-54 27,-54 27,-73\"/>\n",
       "<text text-anchor=\"middle\" x=\"16\" y=\"-61\" font-family=\"sans\" font-size=\"10.00\">1</text>\n",
       "</g>\n",
       "<!-- 2&#45;&gt;3 -->\n",
       "<g id=\"edge4\" class=\"edge\">\n",
       "<title>2&#45;&gt;3</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M16,-80.76C16,-79.36 16,-77.9 16,-76.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"17.05,-76.11 16,-73.11 14.95,-76.11 17.05,-76.11\"/>\n",
       "</g>\n",
       "<!-- 5 -->\n",
       "<g id=\"node6\" class=\"node\">\n",
       "<title>5</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"62,-73 30,-73 30,-54 62,-54 62,-73\"/>\n",
       "<text text-anchor=\"middle\" x=\"46\" y=\"-61\" font-family=\"sans\" font-size=\"10.00\">A.x</text>\n",
       "</g>\n",
       "<!-- 4&#45;&gt;5 -->\n",
       "<g id=\"edge5\" class=\"edge\">\n",
       "<title>4&#45;&gt;5</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M47.3,-80.76C47.19,-79.36 47.07,-77.9 46.96,-76.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"47.97,-76.02 46.69,-73.11 45.88,-76.19 47.97,-76.02\"/>\n",
       "</g>\n",
       "<!-- 7 -->\n",
       "<g id=\"node8\" class=\"node\">\n",
       "<title>7</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"92.5,-73 63.5,-73 63.5,-54 92.5,-54 92.5,-73\"/>\n",
       "<text text-anchor=\"middle\" x=\"78\" y=\"-61\" font-family=\"sans\" font-size=\"10.00\">R1</text>\n",
       "</g>\n",
       "<!-- 4&#45;&gt;7 -->\n",
       "<g id=\"edge6\" class=\"edge\">\n",
       "<title>4&#45;&gt;7</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M58.49,-80.76C60.63,-78.98 62.9,-77.08 65.11,-75.24\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"66.03,-75.84 67.67,-73.11 64.69,-74.23 66.03,-75.84\"/>\n",
       "</g>\n",
       "<!-- 6 -->\n",
       "<g id=\"node7\" class=\"node\">\n",
       "<title>6</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"57,-46 35,-46 35,-27 57,-27 57,-46\"/>\n",
       "<text text-anchor=\"middle\" x=\"46\" y=\"-34\" font-family=\"sans\" font-size=\"10.00\">2</text>\n",
       "</g>\n",
       "<!-- 5&#45;&gt;6 -->\n",
       "<g id=\"edge7\" class=\"edge\">\n",
       "<title>5&#45;&gt;6</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M46,-53.76C46,-52.36 46,-50.9 46,-49.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"47.05,-49.11 46,-46.11 44.95,-49.11 47.05,-49.11\"/>\n",
       "</g>\n",
       "<!-- 8 -->\n",
       "<g id=\"node9\" class=\"node\">\n",
       "<title>8</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"94,-46 62,-46 62,-27 94,-27 94,-46\"/>\n",
       "<text text-anchor=\"middle\" x=\"78\" y=\"-34\" font-family=\"sans\" font-size=\"10.00\">A.x</text>\n",
       "</g>\n",
       "<!-- 7&#45;&gt;8 -->\n",
       "<g id=\"edge8\" class=\"edge\">\n",
       "<title>7&#45;&gt;8</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M78,-53.76C78,-52.36 78,-50.9 78,-49.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"79.05,-49.11 78,-46.11 76.95,-49.11 79.05,-49.11\"/>\n",
       "</g>\n",
       "<!-- 9 -->\n",
       "<g id=\"node10\" class=\"node\">\n",
       "<title>9</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"89,-19 67,-19 67,0 89,0 89,-19\"/>\n",
       "<text text-anchor=\"middle\" x=\"78\" y=\"-7\" font-family=\"sans\" font-size=\"10.00\">3</text>\n",
       "</g>\n",
       "<!-- 8&#45;&gt;9 -->\n",
       "<g id=\"edge9\" class=\"edge\">\n",
       "<title>8&#45;&gt;9</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M78,-26.76C78,-25.36 78,-23.9 78,-22.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"79.05,-22.11 78,-19.11 76.95,-22.11 79.05,-22.11\"/>\n",
       "</g>\n",
       "</g>\n",
       "</svg>\n"
      ],
      "text/plain": [
       "Graph(nodes=[...], edges=[...])"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "A = fact(\n",
    "    'A',\n",
    "    ['x']\n",
    ")\n",
    "RULE = true().interpretation(\n",
    "    A.x\n",
    ").repeatable().interpretation(\n",
    "    A\n",
    ")\n",
    "\n",
    "parser = Parser(RULE)\n",
    "match = parser.match('1 2 3')\n",
    "match.tree.normalized.as_dot"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В `A.x` попадёт \"3\":"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "A(\n",
       "    x='3'\n",
       ")"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "match.fact"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Но бывает нужно сохранить содержание всех повторяющихся вершин-атрибутов, не только самой правой. Помечаем поле как `repeatable`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/svg+xml": [
       "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n",
       "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
       " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
       "<!-- Generated by graphviz version 6.0.2 (20221011.1828)\n",
       " -->\n",
       "<!-- Title: G Pages: 1 -->\n",
       "<svg width=\"182pt\" height=\"189pt\"\n",
       " viewBox=\"0.00 0.00 182.00 189.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
       "<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 185)\">\n",
       "<title>G</title>\n",
       "<polygon fill=\"white\" stroke=\"none\" points=\"-4,4 -4,-185 178,-185 178,4 -4,4\"/>\n",
       "<!-- 0 -->\n",
       "<g id=\"node1\" class=\"node\">\n",
       "<title>0</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"120,-181 84,-181 84,-162 120,-162 120,-181\"/>\n",
       "<text text-anchor=\"middle\" x=\"102\" y=\"-169\" font-family=\"sans\" font-size=\"10.00\">Item</text>\n",
       "</g>\n",
       "<!-- 1 -->\n",
       "<g id=\"node2\" class=\"node\">\n",
       "<title>1</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"116.5,-154 87.5,-154 87.5,-135 116.5,-135 116.5,-154\"/>\n",
       "<text text-anchor=\"middle\" x=\"102\" y=\"-142\" font-family=\"sans\" font-size=\"10.00\">R1</text>\n",
       "</g>\n",
       "<!-- 0&#45;&gt;1 -->\n",
       "<g id=\"edge1\" class=\"edge\">\n",
       "<title>0&#45;&gt;1</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M102,-161.76C102,-160.36 102,-158.9 102,-157.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"103.05,-157.11 102,-154.11 100.95,-157.11 103.05,-157.11\"/>\n",
       "</g>\n",
       "<!-- 2 -->\n",
       "<g id=\"node3\" class=\"node\">\n",
       "<title>2</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"101.5,-127 72.5,-127 72.5,-108 101.5,-108 101.5,-127\"/>\n",
       "<text text-anchor=\"middle\" x=\"87\" y=\"-115\" font-family=\"sans\" font-size=\"10.00\">R2</text>\n",
       "</g>\n",
       "<!-- 1&#45;&gt;2 -->\n",
       "<g id=\"edge2\" class=\"edge\">\n",
       "<title>1&#45;&gt;2</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M96.75,-134.76C95.82,-133.21 94.84,-131.57 93.88,-129.96\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"94.61,-129.14 92.17,-127.11 92.81,-130.23 94.61,-129.14\"/>\n",
       "</g>\n",
       "<!-- 10 -->\n",
       "<g id=\"node11\" class=\"node\">\n",
       "<title>10</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"139.5,-127 110.5,-127 110.5,-108 139.5,-108 139.5,-127\"/>\n",
       "<text text-anchor=\"middle\" x=\"125\" y=\"-115\" font-family=\"sans\" font-size=\"10.00\">R1</text>\n",
       "</g>\n",
       "<!-- 1&#45;&gt;10 -->\n",
       "<g id=\"edge3\" class=\"edge\">\n",
       "<title>1&#45;&gt;10</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M110.04,-134.76C111.61,-133.05 113.27,-131.25 114.9,-129.48\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"115.82,-130.03 117.08,-127.11 114.27,-128.61 115.82,-130.03\"/>\n",
       "</g>\n",
       "<!-- 3 -->\n",
       "<g id=\"node4\" class=\"node\">\n",
       "<title>3</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"70,-100 10,-100 10,-81 70,-81 70,-100\"/>\n",
       "<text text-anchor=\"middle\" x=\"40\" y=\"-88\" font-family=\"sans\" font-size=\"10.00\">Item.titles</text>\n",
       "</g>\n",
       "<!-- 2&#45;&gt;3 -->\n",
       "<g id=\"edge4\" class=\"edge\">\n",
       "<title>2&#45;&gt;3</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M72.39,-108.73C68.1,-106.45 63.36,-103.92 58.85,-101.52\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"59.22,-100.53 56.08,-100.05 58.23,-102.39 59.22,-100.53\"/>\n",
       "</g>\n",
       "<!-- 8 -->\n",
       "<g id=\"node9\" class=\"node\">\n",
       "<title>8</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"101.5,-100 72.5,-100 72.5,-81 101.5,-81 101.5,-100\"/>\n",
       "<text text-anchor=\"middle\" x=\"87\" y=\"-88\" font-family=\"sans\" font-size=\"10.00\">R4</text>\n",
       "</g>\n",
       "<!-- 2&#45;&gt;8 -->\n",
       "<g id=\"edge5\" class=\"edge\">\n",
       "<title>2&#45;&gt;8</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M87,-107.76C87,-106.36 87,-104.9 87,-103.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"88.05,-103.11 87,-100.11 85.95,-103.11 88.05,-103.11\"/>\n",
       "</g>\n",
       "<!-- 4 -->\n",
       "<g id=\"node5\" class=\"node\">\n",
       "<title>4</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"22,-73 0,-73 0,-54 22,-54 22,-73\"/>\n",
       "<text text-anchor=\"middle\" x=\"11\" y=\"-61\" font-family=\"sans\" font-size=\"10.00\">«</text>\n",
       "</g>\n",
       "<!-- 3&#45;&gt;4 -->\n",
       "<g id=\"edge6\" class=\"edge\">\n",
       "<title>3&#45;&gt;4</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M29.86,-80.76C27.79,-78.98 25.59,-77.08 23.46,-75.24\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"23.95,-74.28 20.99,-73.11 22.58,-75.87 23.95,-74.28\"/>\n",
       "</g>\n",
       "<!-- 5 -->\n",
       "<g id=\"node6\" class=\"node\">\n",
       "<title>5</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"52.5,-73 23.5,-73 23.5,-54 52.5,-54 52.5,-73\"/>\n",
       "<text text-anchor=\"middle\" x=\"38\" y=\"-61\" font-family=\"sans\" font-size=\"10.00\">R5</text>\n",
       "</g>\n",
       "<!-- 3&#45;&gt;5 -->\n",
       "<g id=\"edge7\" class=\"edge\">\n",
       "<title>3&#45;&gt;5</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M39.3,-80.76C39.19,-79.36 39.07,-77.9 38.96,-76.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"39.97,-76.02 38.69,-73.11 37.88,-76.19 39.97,-76.02\"/>\n",
       "</g>\n",
       "<!-- 7 -->\n",
       "<g id=\"node8\" class=\"node\">\n",
       "<title>7</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"76,-73 54,-73 54,-54 76,-54 76,-73\"/>\n",
       "<text text-anchor=\"middle\" x=\"65\" y=\"-61\" font-family=\"sans\" font-size=\"10.00\">»</text>\n",
       "</g>\n",
       "<!-- 3&#45;&gt;7 -->\n",
       "<g id=\"edge8\" class=\"edge\">\n",
       "<title>3&#45;&gt;7</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M48.74,-80.76C50.45,-79.05 52.25,-77.25 54.02,-75.48\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"55.01,-75.98 56.39,-73.11 53.52,-74.49 55.01,-75.98\"/>\n",
       "</g>\n",
       "<!-- 6 -->\n",
       "<g id=\"node7\" class=\"node\">\n",
       "<title>6</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"69,-46 7,-46 7,-27 69,-27 69,-46\"/>\n",
       "<text text-anchor=\"middle\" x=\"38\" y=\"-34\" font-family=\"sans\" font-size=\"10.00\">Каштанка</text>\n",
       "</g>\n",
       "<!-- 5&#45;&gt;6 -->\n",
       "<g id=\"edge9\" class=\"edge\">\n",
       "<title>5&#45;&gt;6</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M38,-53.76C38,-52.36 38,-50.9 38,-49.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"39.05,-49.11 38,-46.11 36.95,-49.11 39.05,-49.11\"/>\n",
       "</g>\n",
       "<!-- 9 -->\n",
       "<g id=\"node10\" class=\"node\">\n",
       "<title>9</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"97,-73 77,-73 77,-54 97,-54 97,-73\"/>\n",
       "<text text-anchor=\"middle\" x=\"87\" y=\"-61\" font-family=\"sans\" font-size=\"10.00\">,</text>\n",
       "</g>\n",
       "<!-- 8&#45;&gt;9 -->\n",
       "<g id=\"edge10\" class=\"edge\">\n",
       "<title>8&#45;&gt;9</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M87,-80.76C87,-79.36 87,-77.9 87,-76.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"88.05,-76.11 87,-73.11 85.95,-76.11 88.05,-76.11\"/>\n",
       "</g>\n",
       "<!-- 11 -->\n",
       "<g id=\"node12\" class=\"node\">\n",
       "<title>11</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"164,-100 104,-100 104,-81 164,-81 164,-100\"/>\n",
       "<text text-anchor=\"middle\" x=\"134\" y=\"-88\" font-family=\"sans\" font-size=\"10.00\">Item.titles</text>\n",
       "</g>\n",
       "<!-- 10&#45;&gt;11 -->\n",
       "<g id=\"edge11\" class=\"edge\">\n",
       "<title>10&#45;&gt;11</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M128.15,-107.76C128.71,-106.21 129.29,-104.57 129.87,-102.96\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"130.87,-103.29 130.9,-100.11 128.9,-102.58 130.87,-103.29\"/>\n",
       "</g>\n",
       "<!-- 12 -->\n",
       "<g id=\"node13\" class=\"node\">\n",
       "<title>12</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"120,-73 98,-73 98,-54 120,-54 120,-73\"/>\n",
       "<text text-anchor=\"middle\" x=\"109\" y=\"-61\" font-family=\"sans\" font-size=\"10.00\">«</text>\n",
       "</g>\n",
       "<!-- 11&#45;&gt;12 -->\n",
       "<g id=\"edge12\" class=\"edge\">\n",
       "<title>11&#45;&gt;12</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M125.26,-80.76C123.55,-79.05 121.75,-77.25 119.98,-75.48\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"120.48,-74.49 117.61,-73.11 118.99,-75.98 120.48,-74.49\"/>\n",
       "</g>\n",
       "<!-- 13 -->\n",
       "<g id=\"node14\" class=\"node\">\n",
       "<title>13</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"150.5,-73 121.5,-73 121.5,-54 150.5,-54 150.5,-73\"/>\n",
       "<text text-anchor=\"middle\" x=\"136\" y=\"-61\" font-family=\"sans\" font-size=\"10.00\">R5</text>\n",
       "</g>\n",
       "<!-- 11&#45;&gt;13 -->\n",
       "<g id=\"edge13\" class=\"edge\">\n",
       "<title>11&#45;&gt;13</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M134.7,-80.76C134.81,-79.36 134.93,-77.9 135.04,-76.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"136.12,-76.19 135.31,-73.11 134.03,-76.02 136.12,-76.19\"/>\n",
       "</g>\n",
       "<!-- 17 -->\n",
       "<g id=\"node18\" class=\"node\">\n",
       "<title>17</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"174,-73 152,-73 152,-54 174,-54 174,-73\"/>\n",
       "<text text-anchor=\"middle\" x=\"163\" y=\"-61\" font-family=\"sans\" font-size=\"10.00\">»</text>\n",
       "</g>\n",
       "<!-- 11&#45;&gt;17 -->\n",
       "<g id=\"edge14\" class=\"edge\">\n",
       "<title>11&#45;&gt;17</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M144.14,-80.76C146.21,-78.98 148.41,-77.08 150.54,-75.24\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"151.42,-75.87 153.01,-73.11 150.05,-74.28 151.42,-75.87\"/>\n",
       "</g>\n",
       "<!-- 14 -->\n",
       "<g id=\"node15\" class=\"node\">\n",
       "<title>14</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"138,-46 98,-46 98,-27 138,-27 138,-46\"/>\n",
       "<text text-anchor=\"middle\" x=\"118\" y=\"-34\" font-family=\"sans\" font-size=\"10.00\">Дядя</text>\n",
       "</g>\n",
       "<!-- 13&#45;&gt;14 -->\n",
       "<g id=\"edge15\" class=\"edge\">\n",
       "<title>13&#45;&gt;14</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M129.7,-53.76C128.59,-52.21 127.41,-50.57 126.25,-48.96\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"126.81,-47.93 124.2,-46.11 125.1,-49.16 126.81,-47.93\"/>\n",
       "</g>\n",
       "<!-- 15 -->\n",
       "<g id=\"node16\" class=\"node\">\n",
       "<title>15</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"168.5,-46 139.5,-46 139.5,-27 168.5,-27 168.5,-46\"/>\n",
       "<text text-anchor=\"middle\" x=\"154\" y=\"-34\" font-family=\"sans\" font-size=\"10.00\">R5</text>\n",
       "</g>\n",
       "<!-- 13&#45;&gt;15 -->\n",
       "<g id=\"edge16\" class=\"edge\">\n",
       "<title>13&#45;&gt;15</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M142.3,-53.76C143.41,-52.21 144.59,-50.57 145.75,-48.96\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"146.9,-49.16 147.8,-46.11 145.19,-47.93 146.9,-49.16\"/>\n",
       "</g>\n",
       "<!-- 16 -->\n",
       "<g id=\"node17\" class=\"node\">\n",
       "<title>16</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"173.5,-19 134.5,-19 134.5,0 173.5,0 173.5,-19\"/>\n",
       "<text text-anchor=\"middle\" x=\"154\" y=\"-7\" font-family=\"sans\" font-size=\"10.00\">Ваня</text>\n",
       "</g>\n",
       "<!-- 15&#45;&gt;16 -->\n",
       "<g id=\"edge17\" class=\"edge\">\n",
       "<title>15&#45;&gt;16</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M154,-26.76C154,-25.36 154,-23.9 154,-22.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"155.05,-22.11 154,-19.11 152.95,-22.11 155.05,-22.11\"/>\n",
       "</g>\n",
       "</g>\n",
       "</svg>\n"
      ],
      "text/plain": [
       "Graph(nodes=[...], edges=[...])"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from yargy import not_\n",
    "\n",
    "\n",
    "Item = fact(\n",
    "    'Item',\n",
    "    [attribute('titles').repeatable()]\n",
    ")\n",
    "\n",
    "TITLE = rule(\n",
    "    '«',\n",
    "    not_(eq('»')).repeatable(),\n",
    "    '»'\n",
    ")\n",
    "ITEM = rule(\n",
    "    TITLE.interpretation(\n",
    "        Item.titles\n",
    "    ),\n",
    "    eq(',').optional()\n",
    ").repeatable().interpretation(\n",
    "    Item\n",
    ")\n",
    "\n",
    "parser = Parser(ITEM)\n",
    "text = '«Каштанка», «Дядя Ваня»'\n",
    "match = parser.match(text)\n",
    "match.tree.as_dot"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "«Дядя Ваня» не перезапишет «Каштанка», они оба окажутся в `Item.titles`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Item(\n",
       "    titles=['«Каштанка»',\n",
       "     '«Дядя Ваня»']\n",
       ")"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "match.fact"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Остался последний неочевидный случай, когда ребёнок вершины-конструктора, другая вершина-конструктор. Такая ситуация возникает при использовании рекурсивных грамматик. В примере ребёнок вершины `Item` другая вершина `Item`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/svg+xml": [
       "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n",
       "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
       " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
       "<!-- Generated by graphviz version 6.0.2 (20221011.1828)\n",
       " -->\n",
       "<!-- Title: G Pages: 1 -->\n",
       "<svg width=\"200pt\" height=\"216pt\"\n",
       " viewBox=\"0.00 0.00 200.00 216.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
       "<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 212)\">\n",
       "<title>G</title>\n",
       "<polygon fill=\"white\" stroke=\"none\" points=\"-4,4 -4,-212 196,-212 196,4 -4,4\"/>\n",
       "<!-- 0 -->\n",
       "<g id=\"node1\" class=\"node\">\n",
       "<title>0</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"86,-208 50,-208 50,-189 86,-189 86,-208\"/>\n",
       "<text text-anchor=\"middle\" x=\"68\" y=\"-196\" font-family=\"sans\" font-size=\"10.00\">Item</text>\n",
       "</g>\n",
       "<!-- 1 -->\n",
       "<g id=\"node2\" class=\"node\">\n",
       "<title>1</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"82.5,-181 53.5,-181 53.5,-162 82.5,-162 82.5,-181\"/>\n",
       "<text text-anchor=\"middle\" x=\"68\" y=\"-169\" font-family=\"sans\" font-size=\"10.00\">R1</text>\n",
       "</g>\n",
       "<!-- 0&#45;&gt;1 -->\n",
       "<g id=\"edge1\" class=\"edge\">\n",
       "<title>0&#45;&gt;1</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M68,-188.76C68,-187.36 68,-185.9 68,-184.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"69.05,-184.11 68,-181.11 66.95,-184.11 69.05,-184.11\"/>\n",
       "</g>\n",
       "<!-- 2 -->\n",
       "<g id=\"node3\" class=\"node\">\n",
       "<title>2</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"62,-154 26,-154 26,-135 62,-135 62,-154\"/>\n",
       "<text text-anchor=\"middle\" x=\"44\" y=\"-142\" font-family=\"sans\" font-size=\"10.00\">Item</text>\n",
       "</g>\n",
       "<!-- 1&#45;&gt;2 -->\n",
       "<g id=\"edge2\" class=\"edge\">\n",
       "<title>1&#45;&gt;2</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M59.61,-161.76C57.97,-160.05 56.24,-158.25 54.54,-156.48\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"55.1,-155.55 52.27,-154.11 53.59,-157 55.1,-155.55\"/>\n",
       "</g>\n",
       "<!-- 10 -->\n",
       "<g id=\"node11\" class=\"node\">\n",
       "<title>10</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"123,-154 65,-154 65,-135 123,-135 123,-154\"/>\n",
       "<text text-anchor=\"middle\" x=\"94\" y=\"-142\" font-family=\"sans\" font-size=\"10.00\">Item.date</text>\n",
       "</g>\n",
       "<!-- 1&#45;&gt;10 -->\n",
       "<g id=\"edge3\" class=\"edge\">\n",
       "<title>1&#45;&gt;10</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M77.09,-161.76C78.95,-159.98 80.92,-158.08 82.83,-156.24\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"83.61,-156.95 85.04,-154.11 82.15,-155.43 83.61,-156.95\"/>\n",
       "</g>\n",
       "<!-- 3 -->\n",
       "<g id=\"node4\" class=\"node\">\n",
       "<title>3</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"54.5,-127 25.5,-127 25.5,-108 54.5,-108 54.5,-127\"/>\n",
       "<text text-anchor=\"middle\" x=\"40\" y=\"-115\" font-family=\"sans\" font-size=\"10.00\">R1</text>\n",
       "</g>\n",
       "<!-- 2&#45;&gt;3 -->\n",
       "<g id=\"edge4\" class=\"edge\">\n",
       "<title>2&#45;&gt;3</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M42.6,-134.76C42.38,-133.36 42.14,-131.9 41.91,-130.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"42.89,-129.91 41.38,-127.11 40.82,-130.24 42.89,-129.91\"/>\n",
       "</g>\n",
       "<!-- 4 -->\n",
       "<g id=\"node5\" class=\"node\">\n",
       "<title>4</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"65.5,-100 10.5,-100 10.5,-81 65.5,-81 65.5,-100\"/>\n",
       "<text text-anchor=\"middle\" x=\"38\" y=\"-88\" font-family=\"sans\" font-size=\"10.00\">Item.title</text>\n",
       "</g>\n",
       "<!-- 3&#45;&gt;4 -->\n",
       "<g id=\"edge5\" class=\"edge\">\n",
       "<title>3&#45;&gt;4</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M39.3,-107.76C39.19,-106.36 39.07,-104.9 38.96,-103.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"39.97,-103.02 38.69,-100.11 37.88,-103.19 39.97,-103.02\"/>\n",
       "</g>\n",
       "<!-- 5 -->\n",
       "<g id=\"node6\" class=\"node\">\n",
       "<title>5</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"52.5,-73 23.5,-73 23.5,-54 52.5,-54 52.5,-73\"/>\n",
       "<text text-anchor=\"middle\" x=\"38\" y=\"-61\" font-family=\"sans\" font-size=\"10.00\">R3</text>\n",
       "</g>\n",
       "<!-- 4&#45;&gt;5 -->\n",
       "<g id=\"edge6\" class=\"edge\">\n",
       "<title>4&#45;&gt;5</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M38,-80.76C38,-79.36 38,-77.9 38,-76.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"39.05,-76.11 38,-73.11 36.95,-76.11 39.05,-76.11\"/>\n",
       "</g>\n",
       "<!-- 6 -->\n",
       "<g id=\"node7\" class=\"node\">\n",
       "<title>6</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"22,-46 0,-46 0,-27 22,-27 22,-46\"/>\n",
       "<text text-anchor=\"middle\" x=\"11\" y=\"-34\" font-family=\"sans\" font-size=\"10.00\">«</text>\n",
       "</g>\n",
       "<!-- 5&#45;&gt;6 -->\n",
       "<g id=\"edge7\" class=\"edge\">\n",
       "<title>5&#45;&gt;6</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M28.56,-53.76C26.63,-51.98 24.59,-50.08 22.6,-48.24\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"23.22,-47.38 20.3,-46.11 21.79,-48.92 23.22,-47.38\"/>\n",
       "</g>\n",
       "<!-- 7 -->\n",
       "<g id=\"node8\" class=\"node\">\n",
       "<title>7</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"52.5,-46 23.5,-46 23.5,-27 52.5,-27 52.5,-46\"/>\n",
       "<text text-anchor=\"middle\" x=\"38\" y=\"-34\" font-family=\"sans\" font-size=\"10.00\">R5</text>\n",
       "</g>\n",
       "<!-- 5&#45;&gt;7 -->\n",
       "<g id=\"edge8\" class=\"edge\">\n",
       "<title>5&#45;&gt;7</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M38,-53.76C38,-52.36 38,-50.9 38,-49.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"39.05,-49.11 38,-46.11 36.95,-49.11 39.05,-49.11\"/>\n",
       "</g>\n",
       "<!-- 9 -->\n",
       "<g id=\"node10\" class=\"node\">\n",
       "<title>9</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"76,-46 54,-46 54,-27 76,-27 76,-46\"/>\n",
       "<text text-anchor=\"middle\" x=\"65\" y=\"-34\" font-family=\"sans\" font-size=\"10.00\">»</text>\n",
       "</g>\n",
       "<!-- 5&#45;&gt;9 -->\n",
       "<g id=\"edge9\" class=\"edge\">\n",
       "<title>5&#45;&gt;9</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M47.44,-53.76C49.37,-51.98 51.41,-50.08 53.4,-48.24\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"54.21,-48.92 55.7,-46.11 52.78,-47.38 54.21,-48.92\"/>\n",
       "</g>\n",
       "<!-- 8 -->\n",
       "<g id=\"node9\" class=\"node\">\n",
       "<title>8</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"69,-19 7,-19 7,0 69,0 69,-19\"/>\n",
       "<text text-anchor=\"middle\" x=\"38\" y=\"-7\" font-family=\"sans\" font-size=\"10.00\">Каштанка</text>\n",
       "</g>\n",
       "<!-- 7&#45;&gt;8 -->\n",
       "<g id=\"edge10\" class=\"edge\">\n",
       "<title>7&#45;&gt;8</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M38,-26.76C38,-25.36 38,-23.9 38,-22.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"39.05,-22.11 38,-19.11 36.95,-22.11 39.05,-22.11\"/>\n",
       "</g>\n",
       "<!-- 11 -->\n",
       "<g id=\"node12\" class=\"node\">\n",
       "<title>11</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"114,-127 76,-127 76,-108 114,-108 114,-127\"/>\n",
       "<text text-anchor=\"middle\" x=\"95\" y=\"-115\" font-family=\"sans\" font-size=\"10.00\">Date</text>\n",
       "</g>\n",
       "<!-- 10&#45;&gt;11 -->\n",
       "<g id=\"edge11\" class=\"edge\">\n",
       "<title>10&#45;&gt;11</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M94.35,-134.76C94.41,-133.36 94.46,-131.9 94.52,-130.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"95.58,-130.15 94.66,-127.11 93.49,-130.07 95.58,-130.15\"/>\n",
       "</g>\n",
       "<!-- 12 -->\n",
       "<g id=\"node13\" class=\"node\">\n",
       "<title>12</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"123,-100 67,-100 67,-81 123,-81 123,-100\"/>\n",
       "<text text-anchor=\"middle\" x=\"95\" y=\"-88\" font-family=\"sans\" font-size=\"10.00\">Date.day</text>\n",
       "</g>\n",
       "<!-- 11&#45;&gt;12 -->\n",
       "<g id=\"edge12\" class=\"edge\">\n",
       "<title>11&#45;&gt;12</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M95,-107.76C95,-106.36 95,-104.9 95,-103.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"96.05,-103.11 95,-100.11 93.95,-103.11 96.05,-103.11\"/>\n",
       "</g>\n",
       "<!-- 14 -->\n",
       "<g id=\"node15\" class=\"node\">\n",
       "<title>14</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"192,-100 124,-100 124,-81 192,-81 192,-100\"/>\n",
       "<text text-anchor=\"middle\" x=\"158\" y=\"-88\" font-family=\"sans\" font-size=\"10.00\">Date.month</text>\n",
       "</g>\n",
       "<!-- 11&#45;&gt;14 -->\n",
       "<g id=\"edge13\" class=\"edge\">\n",
       "<title>11&#45;&gt;14</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M114.24,-108.86C120.22,-106.49 126.88,-103.85 133.18,-101.35\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"133.83,-102.22 136.23,-100.14 133.05,-100.27 133.83,-102.22\"/>\n",
       "</g>\n",
       "<!-- 13 -->\n",
       "<g id=\"node14\" class=\"node\">\n",
       "<title>13</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"108.5,-73 81.5,-73 81.5,-54 108.5,-54 108.5,-73\"/>\n",
       "<text text-anchor=\"middle\" x=\"95\" y=\"-61\" font-family=\"sans\" font-size=\"10.00\">18</text>\n",
       "</g>\n",
       "<!-- 12&#45;&gt;13 -->\n",
       "<g id=\"edge14\" class=\"edge\">\n",
       "<title>12&#45;&gt;13</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M95,-80.76C95,-79.36 95,-77.9 95,-76.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"96.05,-76.11 95,-73.11 93.95,-76.11 96.05,-76.11\"/>\n",
       "</g>\n",
       "<!-- 15 -->\n",
       "<g id=\"node16\" class=\"node\">\n",
       "<title>15</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"178.5,-73 137.5,-73 137.5,-54 178.5,-54 178.5,-73\"/>\n",
       "<text text-anchor=\"middle\" x=\"158\" y=\"-61\" font-family=\"sans\" font-size=\"10.00\">июня</text>\n",
       "</g>\n",
       "<!-- 14&#45;&gt;15 -->\n",
       "<g id=\"edge15\" class=\"edge\">\n",
       "<title>14&#45;&gt;15</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M158,-80.76C158,-79.36 158,-77.9 158,-76.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"159.05,-76.11 158,-73.11 156.95,-76.11 159.05,-76.11\"/>\n",
       "</g>\n",
       "</g>\n",
       "</svg>\n"
      ],
      "text/plain": [
       "Graph(nodes=[...], edges=[...])"
      ]
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from yargy import forward, or_\n",
    "\n",
    "Item = fact(\n",
    "    'Item',\n",
    "    ['title', 'date']\n",
    ")\n",
    "\n",
    "ITEM = forward().interpretation(\n",
    "    Item\n",
    ")\n",
    "ITEM.define(or_(\n",
    "    TITLE.interpretation(\n",
    "        Item.title\n",
    "    ),\n",
    "    rule(ITEM, TITLE),\n",
    "    rule(\n",
    "        ITEM,\n",
    "        DATE.interpretation(\n",
    "            Item.date\n",
    "        )\n",
    "    )\n",
    "))\n",
    "\n",
    "parser = Parser(ITEM)\n",
    "text = '«Каштанка» 18 июня'\n",
    "match = parser.match(text)\n",
    "match.tree.as_dot"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В ходе интерпретации появится два объекта: `Item(title='«Каштанка»', date=None)` и `Item(title=None, date=Date('18', 'июня'))`. В конце произойдёт слияние:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Item(\n",
       "    title='«Каштанка»',\n",
       "    date=Date(\n",
       "        year=2017,\n",
       "        month='июня',\n",
       "        day='18'\n",
       "    )\n",
       ")"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "match.fact"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Нормализация"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В Yargy реализованы четыре основных метода для нормализации: `normalized`, `inflected`, `custom` и `const`. `normalized` возвращает нормальную форму слова, соответствует `normal_form` в Pymorphy2:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Date(\n",
       "    year='2015',\n",
       "    month='июня',\n",
       "    day='8'\n",
       ")"
      ]
     },
     "execution_count": 26,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "DATE = rule(\n",
    "    DAY.interpretation(\n",
    "        Date.day\n",
    "    ),\n",
    "    MONTH_NAME.interpretation(\n",
    "        Date.month\n",
    "    ),\n",
    "    YEAR.interpretation(\n",
    "        Date.year\n",
    "    )\n",
    ").interpretation(\n",
    "    Date\n",
    ")\n",
    "\n",
    "parser = Parser(DATE)\n",
    "match = parser.match('8 июня 2015')\n",
    "match.fact"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "С `normalized` слово \"июня\" меняется на \"июнь\":"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Date(\n",
       "    year='2015',\n",
       "    month='июнь',\n",
       "    day='8'\n",
       ")"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "DATE = rule(\n",
    "    DAY.interpretation(\n",
    "        Date.day\n",
    "    ),\n",
    "    MONTH_NAME.interpretation(\n",
    "        Date.month.normalized()\n",
    "    ),\n",
    "    YEAR.interpretation(\n",
    "        Date.year\n",
    "    )\n",
    ").interpretation(\n",
    "    Date\n",
    ")\n",
    "\n",
    "parser = Parser(DATE)\n",
    "match = parser.match('8 июня 2015')\n",
    "match.fact"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Если в `normalized` попадает несколько токенов, каждый приводится к нормальной форме без согласования:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Geo(\n",
       "    name='красный площадь'\n",
       ")"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from yargy.interpretation import fact\n",
    "from yargy.predicates import normalized\n",
    "from IPython.display import display\n",
    "\n",
    "\n",
    "Geo = fact(\n",
    "    'Geo',\n",
    "    ['name']\n",
    ")\n",
    "\n",
    "RULE = rule(\n",
    "    normalized('Красная'),\n",
    "    normalized('площадь')\n",
    ").interpretation(\n",
    "    Geo.name.normalized()\n",
    ").interpretation(\n",
    "    Geo\n",
    ")\n",
    "\n",
    "parser = Parser(RULE)\n",
    "for match in parser.findall('на Красной площади'):\n",
    "    display(match.fact)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Особым образом ведёт себя `normalized`, когда идёт после газеттира. Результат нормализации — ключ газеттира:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Geo(\n",
       "    name='красная площадь'\n",
       ")"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": [
       "Geo(\n",
       "    name='первомайская улица'\n",
       ")"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from yargy.pipelines import morph_pipeline\n",
    "\n",
    "RULE = morph_pipeline([\n",
    "    'красная площадь',\n",
    "    'первомайская улица'\n",
    "]).interpretation(\n",
    "    Geo.name.normalized()\n",
    ").interpretation(\n",
    "    Geo\n",
    ")\n",
    "\n",
    "parser = Parser(RULE)\n",
    "for match in parser.findall('c Красной площади на Первомайскую улицу'):\n",
    "    display(match.fact)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`inflected` склоняет слово, соответствует методу `inflect` в Pymorphy2:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Name(\n",
       "    first='саша'\n",
       ")"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": [
       "Name(\n",
       "    first='маша'\n",
       ")"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": [
       "Name(\n",
       "    first='вадим'\n",
       ")"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from yargy.interpretation import fact\n",
    "from yargy.predicates import gram\n",
    "\n",
    "Name = fact(\n",
    "    'Name',\n",
    "    ['first']\n",
    ")\n",
    "\n",
    "NAME = gram('Name').interpretation(\n",
    "    Name.first.inflected()\n",
    ").interpretation(\n",
    "    Name\n",
    ")\n",
    "\n",
    "parser = Parser(NAME)\n",
    "for match in parser.findall('Саше, Маше, Вадиму'):\n",
    "    display(match.fact)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`inflected` принимает набор граммем:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Name(\n",
       "    first='саш'\n",
       ")"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": [
       "Name(\n",
       "    first='маш'\n",
       ")"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": [
       "Name(\n",
       "    first='вадимов'\n",
       ")"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "NAME = gram('Name').interpretation(\n",
    "    Name.first.inflected({'accs', 'plur'})  # винительный падеж, множественное число\n",
    ").interpretation(\n",
    "    Name\n",
    ")\n",
    "\n",
    "parser = Parser(NAME)\n",
    "for match in parser.findall('Саша, Маша, Вадим'):\n",
    "    display(match.fact)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`custom` применяет к слову произвольную функцию:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Float(\n",
       "    value=3.1415\n",
       ")"
      ]
     },
     "execution_count": 32,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from yargy.interpretation import fact\n",
    "from yargy.predicates import type\n",
    "\n",
    "Float = fact(\n",
    "    'Float',\n",
    "    ['value']\n",
    ")\n",
    "\n",
    "\n",
    "INT = type('INT')\n",
    "FLOAT = rule(\n",
    "    INT,\n",
    "    '.',\n",
    "    INT\n",
    ").interpretation(\n",
    "    Float.value.custom(float)\n",
    ").interpretation(\n",
    "    Float\n",
    ")\n",
    "\n",
    "parser = Parser(FLOAT)\n",
    "match = parser.match('3.1415')\n",
    "match.fact"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`custom` может применяться вместе с `normalized`. Тогда слово начала ставится в нормальную форму, потом к нему применяется функция:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Date(\n",
       "    year=2015,\n",
       "    month=6,\n",
       "    day=8\n",
       ")"
      ]
     },
     "execution_count": 33,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "MONTHS = {\n",
    "    'январь': 1,\n",
    "    'февраль': 2,\n",
    "    'март': 3,\n",
    "    'апрель': 4,\n",
    "    'мая': 5,\n",
    "    'июнь': 6,\n",
    "    'июль': 7,\n",
    "    'август': 8,\n",
    "    'сентябрь': 9,\n",
    "    'октябрь': 10,\n",
    "    'ноябрь': 11,\n",
    "    'декабрь': 12\n",
    "}\n",
    "\n",
    "DATE = rule(\n",
    "    DAY.interpretation(\n",
    "        Date.day.custom(int)\n",
    "    ),\n",
    "    MONTH_NAME.interpretation(\n",
    "        Date.month.normalized().custom(MONTHS.__getitem__)\n",
    "    ),\n",
    "    YEAR.interpretation(\n",
    "        Date.year.custom(int)\n",
    "    )\n",
    ").interpretation(\n",
    "    Date\n",
    ")\n",
    "\n",
    "parser = Parser(DATE)\n",
    "match = parser.match('8 июня 2015')\n",
    "match.fact"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`const` просто заменяет слово или словосочетания фиксированным значением:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Era(\n",
       "    value='AD'\n",
       ")"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": [
       "Era(\n",
       "    value='BC'\n",
       ")"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "Era = fact(\n",
    "    'Era',\n",
    "    ['value']\n",
    ")\n",
    "\n",
    "BC = morph_pipeline([\n",
    "    'до нашей эры',\n",
    "    'до н.э.'\n",
    "]).interpretation(\n",
    "    Era.value.const('BC')\n",
    ")\n",
    "AD = morph_pipeline([\n",
    "    'наша эра',\n",
    "    'н.э.'\n",
    "]).interpretation(\n",
    "    Era.value.const('AD')\n",
    ")\n",
    "ERA = or_(\n",
    "    BC,\n",
    "    AD\n",
    ").interpretation(\n",
    "    Era\n",
    ")\n",
    "\n",
    "parser = Parser(ERA)\n",
    "for match in parser.findall('наша эра, до н.э.'):\n",
    "    display(match.fact)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Согласование"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В Yargy реализовано четыре типа согласований: `gender_relation` — согласование по роду, `number_relation` — по числу, `case_relation` — по падежу, `gnc_relation` — по роду, числу и падежу. Метод `match` указывает согласование:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Name(\n",
       "    first='саша',\n",
       "    last='иванова'\n",
       ")"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/svg+xml": [
       "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n",
       "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
       " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
       "<!-- Generated by graphviz version 6.0.2 (20221011.1828)\n",
       " -->\n",
       "<!-- Title: G Pages: 1 -->\n",
       "<svg width=\"274pt\" height=\"143pt\"\n",
       " viewBox=\"0.00 0.00 274.00 143.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
       "<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 139)\">\n",
       "<title>G</title>\n",
       "<polygon fill=\"white\" stroke=\"none\" points=\"-4,4 -4,-139 270,-139 270,4 -4,4\"/>\n",
       "<!-- 0 -->\n",
       "<g id=\"node1\" class=\"node\">\n",
       "<title>0</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"176.5,-135 133.5,-135 133.5,-116 176.5,-116 176.5,-135\"/>\n",
       "<text text-anchor=\"middle\" x=\"155\" y=\"-123\" font-family=\"sans\" font-size=\"10.00\">Name</text>\n",
       "</g>\n",
       "<!-- 1 -->\n",
       "<g id=\"node2\" class=\"node\">\n",
       "<title>1</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"176,-99 0,-99 0,-80 176,-80 176,-99\"/>\n",
       "<text text-anchor=\"middle\" x=\"88\" y=\"-87\" font-family=\"sans\" font-size=\"10.00\">Name.first.inflected(nomn, sing)^gnc</text>\n",
       "</g>\n",
       "<!-- 0&#45;&gt;1 -->\n",
       "<g id=\"edge1\" class=\"edge\">\n",
       "<title>0&#45;&gt;1</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M138.09,-115.92C128.96,-111.29 117.62,-105.53 108.05,-100.67\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"108.28,-99.62 105.13,-99.2 107.33,-101.49 108.28,-99.62\"/>\n",
       "</g>\n",
       "<!-- 3 -->\n",
       "<g id=\"node4\" class=\"node\">\n",
       "<title>3</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"266,-55 90,-55 90,-36 266,-36 266,-55\"/>\n",
       "<text text-anchor=\"middle\" x=\"178\" y=\"-43\" font-family=\"sans\" font-size=\"10.00\">Name.last.inflected(nomn, sing)^gnc</text>\n",
       "</g>\n",
       "<!-- 0&#45;&gt;3 -->\n",
       "<g id=\"edge2\" class=\"edge\">\n",
       "<title>0&#45;&gt;3</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M162.93,-115.72C166.64,-111.08 170.74,-105.1 173,-99 177.9,-85.77 178.71,-69.45 178.58,-58.55\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"179.63,-58.33 178.52,-55.35 177.53,-58.37 179.63,-58.33\"/>\n",
       "</g>\n",
       "<!-- 2 -->\n",
       "<g id=\"node3\" class=\"node\">\n",
       "<title>2</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"89,-55 47,-55 47,-36 89,-36 89,-55\"/>\n",
       "<text text-anchor=\"middle\" x=\"68\" y=\"-43\" font-family=\"sans\" font-size=\"10.00\">Сашу</text>\n",
       "</g>\n",
       "<!-- 1&#45;&gt;2 -->\n",
       "<g id=\"edge3\" class=\"edge\">\n",
       "<title>1&#45;&gt;2</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M83.76,-79.59C80.75,-73.27 76.68,-64.73 73.43,-57.91\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"74.34,-57.36 72.1,-55.11 72.44,-58.27 74.34,-57.36\"/>\n",
       "</g>\n",
       "<!-- 1&#45;&gt;3 -->\n",
       "<g id=\"edge5\" class=\"edge\">\n",
       "<title>1&#45;&gt;3</title>\n",
       "<path fill=\"none\" stroke=\"gray\" stroke-dasharray=\"5,2\" d=\"M106.65,-79.8C122.2,-72.54 144.23,-62.26 159.68,-55.05\"/>\n",
       "<text text-anchor=\"middle\" x=\"148.5\" y=\"-65.6\" font-family=\"sans\" font-size=\"8.00\" fill=\"gray\">gnc</text>\n",
       "</g>\n",
       "<!-- 4 -->\n",
       "<g id=\"node5\" class=\"node\">\n",
       "<title>4</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"206,-19 150,-19 150,0 206,0 206,-19\"/>\n",
       "<text text-anchor=\"middle\" x=\"178\" y=\"-7\" font-family=\"sans\" font-size=\"10.00\">Иванову</text>\n",
       "</g>\n",
       "<!-- 3&#45;&gt;4 -->\n",
       "<g id=\"edge4\" class=\"edge\">\n",
       "<title>3&#45;&gt;4</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M178,-35.92C178,-31.91 178,-27.06 178,-22.67\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"179.05,-22.43 178,-19.43 176.95,-22.43 179.05,-22.43\"/>\n",
       "</g>\n",
       "</g>\n",
       "</svg>\n"
      ],
      "text/plain": [
       "Graph(nodes=[...], edges=[...])"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from yargy.relations import gnc_relation\n",
    "\n",
    "Name = fact(\n",
    "    'Name',\n",
    "    ['first', 'last']\n",
    ")\n",
    "\n",
    "gnc = gnc_relation()\n",
    "\n",
    "NAME = rule(\n",
    "    gram('Name').interpretation(\n",
    "        Name.first.inflected()\n",
    "    ).match(gnc),\n",
    "    gram('Surn').interpretation(\n",
    "        Name.last.inflected()\n",
    "    ).match(gnc)\n",
    ").interpretation(\n",
    "    Name\n",
    ")\n",
    "\n",
    "parser = Parser(NAME)\n",
    "match = parser.match('Сашу Иванову')\n",
    "display(match.fact)\n",
    "display(match.tree.as_dot)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`main` указывает на главное слово во фразе. По-умолчанию главное слово — самое левое:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/svg+xml": [
       "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n",
       "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
       " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
       "<!-- Generated by graphviz version 6.0.2 (20221011.1828)\n",
       " -->\n",
       "<!-- Title: G Pages: 1 -->\n",
       "<svg width=\"303pt\" height=\"81pt\"\n",
       " viewBox=\"0.00 0.00 302.50 81.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
       "<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 77)\">\n",
       "<title>G</title>\n",
       "<polygon fill=\"white\" stroke=\"none\" points=\"-4,4 -4,-77 298.5,-77 298.5,4 -4,4\"/>\n",
       "<!-- 0 -->\n",
       "<g id=\"node1\" class=\"node\">\n",
       "<title>0</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"164,-73 127,-73 127,-54 164,-54 164,-73\"/>\n",
       "<text text-anchor=\"middle\" x=\"145.5\" y=\"-61\" font-family=\"sans\" font-size=\"10.00\">Rule</text>\n",
       "</g>\n",
       "<!-- 1 -->\n",
       "<g id=\"node2\" class=\"node\">\n",
       "<title>1</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"177,-46 114,-46 114,-27 177,-27 177,-46\"/>\n",
       "<text text-anchor=\"middle\" x=\"145.5\" y=\"-34\" font-family=\"sans\" font-size=\"10.00\">Production</text>\n",
       "</g>\n",
       "<!-- 0&#45;&gt;1 -->\n",
       "<g id=\"edge1\" class=\"edge\">\n",
       "<title>0&#45;&gt;1</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M145.5,-53.76C145.5,-52.36 145.5,-50.9 145.5,-49.45\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"146.55,-49.11 145.5,-46.11 144.45,-49.11 146.55,-49.11\"/>\n",
       "</g>\n",
       "<!-- 2 -->\n",
       "<g id=\"node3\" class=\"node\">\n",
       "<title>2</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"143,-19 0,-19 0,0 143,0 143,-19\"/>\n",
       "<text text-anchor=\"middle\" x=\"71.5\" y=\"-7\" font-family=\"sans\" font-size=\"10.00\">normalized(value=&#39;главный&#39;)</text>\n",
       "</g>\n",
       "<!-- 1&#45;&gt;2 -->\n",
       "<g id=\"edge2\" class=\"edge\">\n",
       "<title>1&#45;&gt;2</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M120.03,-26.9C113.45,-24.67 106.32,-22.26 99.61,-20\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"99.94,-19 96.76,-19.03 99.27,-20.99 99.94,-19\"/>\n",
       "</g>\n",
       "<!-- 3 -->\n",
       "<g id=\"node4\" class=\"node\">\n",
       "<title>3</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"294.5,-19 144.5,-19 144.5,0 294.5,0 294.5,-19\"/>\n",
       "<text text-anchor=\"middle\" x=\"219.5\" y=\"-7\" font-family=\"sans\" font-size=\"10.00\">normalized(value=&#39;бухгалтер&#39;)</text>\n",
       "</g>\n",
       "<!-- 1&#45;&gt;3 -->\n",
       "<g id=\"edge3\" class=\"edge\">\n",
       "<title>1&#45;&gt;3</title>\n",
       "<path fill=\"none\" stroke=\"#888888\" d=\"M170.97,-26.9C177.55,-24.67 184.68,-22.26 191.39,-20\"/>\n",
       "<polygon fill=\"#888888\" stroke=\"#888888\" points=\"191.73,-20.99 194.24,-19.03 191.06,-19 191.73,-20.99\"/>\n",
       "</g>\n",
       "</g>\n",
       "</svg>\n"
      ],
      "text/plain": [
       "Graph(nodes=[...], edges=[...])"
      ]
     },
     "execution_count": 36,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from yargy.relations import main\n",
    "\n",
    "POSITION = rule(\n",
    "    normalized('главный'),\n",
    "    main(normalized('бухгалтер'))\n",
    ")\n",
    "\n",
    "POSITION.as_dot"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/svg+xml": [
       "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n",
       "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
       " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
       "<!-- Generated by graphviz version 6.0.2 (20221011.1828)\n",
       " -->\n",
       "<!-- Title: G Pages: 1 -->\n",
       "<svg width=\"280pt\" height=\"223pt\"\n",
       " viewBox=\"0.00 0.00 280.00 223.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
       "<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 219)\">\n",
       "<title>G</title>\n",
       "<polygon fill=\"white\" stroke=\"none\" points=\"-4,4 -4,-219 276,-219 276,4 -4,4\"/>\n",
       "<!-- 0 -->\n",
       "<g id=\"node1\" class=\"node\">\n",
       "<title>0</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"142.5,-215 113.5,-215 113.5,-196 142.5,-196 142.5,-215\"/>\n",
       "<text text-anchor=\"middle\" x=\"128\" y=\"-203\" font-family=\"sans\" font-size=\"10.00\">R0</text>\n",
       "</g>\n",
       "<!-- 1 -->\n",
       "<g id=\"node2\" class=\"node\">\n",
       "<title>1</title>\n",
       "<polygon fill=\"#aec7e8\" stroke=\"none\" points=\"140.5,-179 85.5,-179 85.5,-160 140.5,-160 140.5,-179\"/>\n",
       "<text text-anchor=\"middle\" x=\"113\" y=\"-167\" font-family=\"sans\" font-size=\"10.00\">R1^case</text>\n",
       "</g>\n",
       "<!-- 0&#45;&gt;1 -->\n",
       "<g id=\"edge1\" class=\"edge\">\n",
       "<title>0&#45;&gt;1</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M124.21,-195.92C122.41,-191.83 120.22,-186.87 118.26,-182.42\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"119.11,-181.75 116.94,-179.43 117.19,-182.6 119.11,-181.75\"/>\n",
       "</g>\n",
       "<!-- 4 -->\n",
       "<g id=\"node5\" class=\"node\">\n",
       "<title>4</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"194,-135 126,-135 126,-116 194,-116 194,-135\"/>\n",
       "<text text-anchor=\"middle\" x=\"160\" y=\"-123\" font-family=\"sans\" font-size=\"10.00\">Name^case</text>\n",
       "</g>\n",
       "<!-- 0&#45;&gt;4 -->\n",
       "<g id=\"edge2\" class=\"edge\">\n",
       "<title>0&#45;&gt;4</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M131.55,-195.85C137.28,-181.87 148.61,-154.27 155.11,-138.43\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"156.17,-138.6 156.34,-135.42 154.23,-137.8 156.17,-138.6\"/>\n",
       "</g>\n",
       "<!-- 2 -->\n",
       "<g id=\"node3\" class=\"node\">\n",
       "<title>2</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"56,-135 0,-135 0,-116 56,-116 56,-135\"/>\n",
       "<text text-anchor=\"middle\" x=\"28\" y=\"-123\" font-family=\"sans\" font-size=\"10.00\">главного</text>\n",
       "</g>\n",
       "<!-- 1&#45;&gt;2 -->\n",
       "<g id=\"edge3\" class=\"edge\">\n",
       "<title>1&#45;&gt;2</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M95.39,-159.8C81.62,-152.99 62.47,-143.53 48.11,-136.43\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"48.45,-135.44 45.3,-135.05 47.52,-137.32 48.45,-135.44\"/>\n",
       "</g>\n",
       "<!-- 3 -->\n",
       "<g id=\"node4\" class=\"node\">\n",
       "<title>3</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"125,-135 57,-135 57,-116 125,-116 125,-135\"/>\n",
       "<text text-anchor=\"middle\" x=\"91\" y=\"-123\" font-family=\"sans\" font-size=\"10.00\">бухгалтера</text>\n",
       "</g>\n",
       "<!-- 1&#45;&gt;3 -->\n",
       "<g id=\"edge4\" class=\"edge\">\n",
       "<title>1&#45;&gt;3</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M108.33,-159.59C105.02,-153.27 100.55,-144.73 96.98,-137.91\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"97.83,-137.28 95.51,-135.11 95.97,-138.25 97.83,-137.28\"/>\n",
       "</g>\n",
       "<!-- 1&#45;&gt;4 -->\n",
       "<g id=\"edge9\" class=\"edge\">\n",
       "<title>1&#45;&gt;4</title>\n",
       "<path fill=\"none\" stroke=\"gray\" stroke-dasharray=\"5,2\" d=\"M115.51,-159.56C117.39,-154.21 120.43,-147.57 125,-143 128.13,-139.86 131.98,-137.25 135.96,-135.09\"/>\n",
       "<text text-anchor=\"middle\" x=\"133.5\" y=\"-145.6\" font-family=\"sans\" font-size=\"8.00\" fill=\"gray\">case</text>\n",
       "</g>\n",
       "<!-- 5 -->\n",
       "<g id=\"node6\" class=\"node\">\n",
       "<title>5</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"182,-99 6,-99 6,-80 182,-80 182,-99\"/>\n",
       "<text text-anchor=\"middle\" x=\"94\" y=\"-87\" font-family=\"sans\" font-size=\"10.00\">Name.first.inflected(nomn, sing)^gnc</text>\n",
       "</g>\n",
       "<!-- 4&#45;&gt;5 -->\n",
       "<g id=\"edge5\" class=\"edge\">\n",
       "<title>4&#45;&gt;5</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M143.35,-115.92C134.35,-111.29 123.18,-105.53 113.75,-100.67\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"114.03,-99.64 110.88,-99.2 113.06,-101.5 114.03,-99.64\"/>\n",
       "</g>\n",
       "<!-- 7 -->\n",
       "<g id=\"node8\" class=\"node\">\n",
       "<title>7</title>\n",
       "<polygon fill=\"#dbdb8d\" stroke=\"none\" points=\"272,-55 96,-55 96,-36 272,-36 272,-55\"/>\n",
       "<text text-anchor=\"middle\" x=\"184\" y=\"-43\" font-family=\"sans\" font-size=\"10.00\">Name.last.inflected(nomn, sing)^gnc</text>\n",
       "</g>\n",
       "<!-- 4&#45;&gt;7 -->\n",
       "<g id=\"edge6\" class=\"edge\">\n",
       "<title>4&#45;&gt;7</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M168.37,-115.79C172.3,-111.17 176.62,-105.18 179,-99 184.13,-85.68 184.87,-69.12 184.66,-58.2\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"185.7,-57.97 184.57,-55 183.6,-58.03 185.7,-57.97\"/>\n",
       "</g>\n",
       "<!-- 6 -->\n",
       "<g id=\"node7\" class=\"node\">\n",
       "<title>6</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"95,-55 45,-55 45,-36 95,-36 95,-55\"/>\n",
       "<text text-anchor=\"middle\" x=\"70\" y=\"-43\" font-family=\"sans\" font-size=\"10.00\">марину</text>\n",
       "</g>\n",
       "<!-- 5&#45;&gt;6 -->\n",
       "<g id=\"edge7\" class=\"edge\">\n",
       "<title>5&#45;&gt;6</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M88.91,-79.59C85.3,-73.27 80.42,-64.73 76.52,-57.91\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"77.32,-57.19 74.92,-55.11 75.5,-58.23 77.32,-57.19\"/>\n",
       "</g>\n",
       "<!-- 5&#45;&gt;7 -->\n",
       "<g id=\"edge10\" class=\"edge\">\n",
       "<title>5&#45;&gt;7</title>\n",
       "<path fill=\"none\" stroke=\"gray\" stroke-dasharray=\"5,2\" d=\"M112.65,-79.8C128.2,-72.54 150.23,-62.26 165.68,-55.05\"/>\n",
       "<text text-anchor=\"middle\" x=\"154.5\" y=\"-65.6\" font-family=\"sans\" font-size=\"8.00\" fill=\"gray\">gnc</text>\n",
       "</g>\n",
       "<!-- 8 -->\n",
       "<g id=\"node9\" class=\"node\">\n",
       "<title>8</title>\n",
       "<polygon fill=\"#eeeeee\" stroke=\"none\" points=\"210.5,-19 157.5,-19 157.5,0 210.5,0 210.5,-19\"/>\n",
       "<text text-anchor=\"middle\" x=\"184\" y=\"-7\" font-family=\"sans\" font-size=\"10.00\">игореву</text>\n",
       "</g>\n",
       "<!-- 7&#45;&gt;8 -->\n",
       "<g id=\"edge8\" class=\"edge\">\n",
       "<title>7&#45;&gt;8</title>\n",
       "<path fill=\"none\" stroke=\"gray\" d=\"M184,-35.92C184,-31.91 184,-27.06 184,-22.67\"/>\n",
       "<polygon fill=\"gray\" stroke=\"gray\" points=\"185.05,-22.43 184,-19.43 182.95,-22.43 185.05,-22.43\"/>\n",
       "</g>\n",
       "</g>\n",
       "</svg>\n"
      ],
      "text/plain": [
       "Graph(nodes=[...], edges=[...])"
      ]
     },
     "execution_count": 37,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from yargy.relations import case_relation\n",
    "\n",
    "case = case_relation()\n",
    "\n",
    "PERSON = rule(\n",
    "    POSITION.match(case),\n",
    "    NAME.match(case)\n",
    ")\n",
    "\n",
    "\n",
    "parser = Parser(PERSON)\n",
    "assert not parser.match('главного бухгалтер марину игореву')\n",
    "\n",
    "match = parser.match('главного бухгалтера марину игореву')\n",
    "match.tree.as_dot"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
